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
*
* This file is part of nymea.
@ -32,12 +32,15 @@
#include "nymeasettings.h"
#include "loggingcategories.h"
#include <math.h>
#include <network/ping.h>
#include <network/arpsocket.h>
#include <network/networkutils.h>
#define CACHE_VERSION 1
NYMEA_LOGGING_CATEGORY(dcNetworkDeviceDiscovery, "NetworkDeviceDiscovery")
namespace nymeaserver {
@ -86,7 +89,7 @@ NetworkDeviceDiscoveryImpl::NetworkDeviceDiscoveryImpl(QObject *parent) :
// Timer for updating the monitors
m_monitorTimer = new QTimer(this);
m_monitorTimer->setInterval(5000);
m_monitorTimer->setInterval(10000);
m_monitorTimer->setSingleShot(false);
connect(m_monitorTimer, &QTimer::timeout, this, &NetworkDeviceDiscoveryImpl::evaluateMonitors);
@ -128,19 +131,19 @@ NetworkDeviceDiscoveryReply *NetworkDeviceDiscoveryImpl::discover()
} else {
qCDebug(dcNetworkDeviceDiscovery()) << "Starting internally a new discovery.";
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](){
// Finish all pending replies
foreach (NetworkDeviceDiscoveryReplyImpl *reply, m_pendingDiscoveryReplies) {
// Sync all network device infos with all pending replies
foreach (const NetworkDeviceInfo &info, m_currentDiscoveryReply->networkDeviceInfos()) {
reply->addCompleteNetworkDeviceInfo(info);
}
}
foreach (const NetworkDeviceInfo &info, m_currentDiscoveryReply->virtualNetworkDeviceInfos()) {
reply->addVirtualNetworkDeviceInfo(info);
}
// Update local cache right after a discovery finished
foreach (const NetworkDeviceInfo &info, m_currentDiscoveryReply->networkDeviceInfos()) {
updateCache(info);
}
// Delete the current reply before finishing the pending replies.
@ -159,8 +162,6 @@ NetworkDeviceDiscoveryReply *NetworkDeviceDiscoveryImpl::discover()
// Create the reply for the user
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);
if (!available()) {
@ -171,19 +172,23 @@ NetworkDeviceDiscoveryReply *NetworkDeviceDiscoveryImpl::discover()
}
if (alreadyRunning) {
// Add already discovered network device infos in the next event loop
// so any connections after this method call will work as expected
// Emit allready discovered hosts so any integration can implement safly a connect on the host hostAddressDiscovered signal
// even if the internal discvoery was already running
QTimer::singleShot(0, reply, [this, reply](){
if (!m_currentDiscoveryReply)
return;
foreach (const NetworkDeviceInfo &networkDeviceInfo, m_currentDiscoveryReply->networkDeviceInfos()) {
reply->addCompleteNetworkDeviceInfo(networkDeviceInfo);
foreach (const QHostAddress &address, m_currentDiscoveryReply->currentCache().keys()) {
reply->hostAddressDiscovered(address);
}
connect(m_currentDiscoveryReply, &NetworkDeviceDiscoveryReplyImpl::hostAddressDiscovered, reply, &NetworkDeviceDiscoveryReplyImpl::hostAddressDiscovered);
});
} else {
qCInfo(dcNetworkDeviceDiscovery()) << "Starting network device discovery ...";
connect(m_currentDiscoveryReply, &NetworkDeviceDiscoveryReplyImpl::hostAddressDiscovered, reply, &NetworkDeviceDiscoveryReplyImpl::hostAddressDiscovered);
if (m_ping->available())
pingAllNetworkDevices();
@ -213,74 +218,101 @@ bool NetworkDeviceDiscoveryImpl::running() const
return m_running;
}
NetworkDeviceMonitor *NetworkDeviceDiscoveryImpl::registerMonitor(const MacAddress &macAddress)
NetworkDeviceMonitor *NetworkDeviceDiscoveryImpl::registerMonitor(Thing *thing)
{
if (macAddress.isNull()) {
qCWarning(dcNetworkDeviceDiscovery()) << "Could not register monitor for invalid" << macAddress;
if (!thing->thingClass().interfaces().contains("networkdevice")) {
qCWarning(dcNetworkDeviceDiscovery()) << "Cannot register network device monitor because the thing"
<< thing << "does not implement the \"networkdevice\" interface.";
return nullptr;
}
// Make sure we create only one monitor per MAC and keep track how many user
// have access to this monitor otherwise an unregister could cause a crash in
// an other plugin plugin which might still need it
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);
}
MacAddress macAddress(thing->paramValue("macAddress").toString());
QString hostName = thing->paramValue("hostName").toString();
QHostAddress address(thing->paramValue("address").toString());
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
NetworkDeviceInfo info;
if (m_networkInfoCache.contains(macAddress)) {
info = m_networkInfoCache.value(macAddress);
} else {
info.setMacAddress(macAddress.toString());
}
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;
// Check if we already have a monitor for these settings...
NetworkDeviceMonitorImpl *internalMonitor = nullptr;
for (QHash<NetworkDeviceMonitorImpl *, QVector<NetworkDeviceMonitorImpl *>>::const_iterator iterator = m_monitors.cbegin(),
end = m_monitors.cend(); iterator != end; ++iterator) {
if (iterator.key()->address() == address && iterator.key()->macAddress() == macAddress && iterator.key()->hostName() == hostName) {
internalMonitor = iterator.key();
break;
}
}
if (m_monitors.contains(macAddress)) {
NetworkDeviceMonitorImpl *monitor = m_monitors.take(macAddress);
qCInfo(dcNetworkDeviceDiscovery()) << "Unregister" << monitor;
monitor->deleteLater();
m_monitorsReferenceCount.remove(macAddress);
bool newMonitor = true;
if (internalMonitor) {
qCDebug(dcNetworkDeviceDiscovery()) << "Already have an internal monitor for this network device" << internalMonitor;
newMonitor = false;
} 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)
@ -288,10 +320,7 @@ void NetworkDeviceDiscoveryImpl::unregisterMonitor(NetworkDeviceMonitor *network
if (!networkDeviceMonitor)
return;
if (!m_monitors.values().contains(qobject_cast<NetworkDeviceMonitorImpl *>(networkDeviceMonitor)))
return;
unregisterMonitor(MacAddress(networkDeviceMonitor->networkDeviceInfo().macAddress()));
cleanupPluginMonitor(qobject_cast<NetworkDeviceMonitorImpl *>(networkDeviceMonitor));
}
PingReply *NetworkDeviceDiscoveryImpl::ping(const QHostAddress &address, uint retries)
@ -302,6 +331,14 @@ PingReply *NetworkDeviceDiscoveryImpl::ping(const QHostAddress &address, uint re
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 *reply = m_ping->ping(address, lookupHost, retries);
@ -328,7 +365,7 @@ bool NetworkDeviceDiscoveryImpl::sendArpRequest(const QHostAddress &address)
return false;
}
QHash<MacAddress, NetworkDeviceInfo> NetworkDeviceDiscoveryImpl::cache() const
NetworkDeviceInfos NetworkDeviceDiscoveryImpl::cache() const
{
return m_networkInfoCache;
}
@ -449,7 +486,35 @@ void NetworkDeviceDiscoveryImpl::processMonitorPingResult(PingReply *reply, Netw
// Save the last time we tried to communicate
if (reply->error() == PingReply::ErrorNoError) {
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);
} else {
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)
{
connect(reply, &PingReply::finished, this, [=](){
// Search cache for mac address and update last seen
connect(reply, &PingReply::finished, this, [this, reply](){
if (reply->error() == PingReply::ErrorNoError) {
foreach (const NetworkDeviceInfo &info, m_networkInfoCache) {
if (info.address() == reply->targetHostAddress()) {
// Found info for this ip, update the cache
MacAddress macAddress(info.macAddress());
if (!macAddress.isNull() && m_networkInfoCache.contains(macAddress)) {
m_lastSeen[macAddress] = QDateTime::currentDateTime();
saveNetworkDeviceCache(m_networkInfoCache.value(macAddress));
}
}
}
int index = m_networkInfoCache.indexFromHostAddress(reply->targetHostAddress());
if (index < 0)
return;
m_lastSeen[reply->targetHostAddress()] = QDateTime::currentDateTime();
saveNetworkDeviceCache(m_networkInfoCache.at(index));
}
// Update any monitor
foreach (NetworkDeviceMonitorImpl *monitor, m_monitors.values()) {
if (monitor->networkDeviceInfo().address() == reply->targetHostAddress()) {
// Update any relevant monitor
foreach (NetworkDeviceMonitorImpl *monitor, m_monitors.keys()) {
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);
}
}
@ -488,41 +551,61 @@ void NetworkDeviceDiscoveryImpl::loadNetworkDeviceCache()
qCInfo(dcNetworkDeviceDiscovery()) << "Loading cached network device information from" << m_cacheSettings->fileName();
m_networkInfoCache.clear();
QDateTime now = QDateTime::currentDateTime();
QDateTime currentDateTime = QDateTime::currentDateTime();
m_cacheSettings->beginGroup("NetworkDeviceInfos");
foreach (const QString &macAddress, m_cacheSettings->childGroups()) {
m_cacheSettings->beginGroup(macAddress);
uint cacheVersion = m_cacheSettings->value("version", 0).toUInt();
MacAddress mac(macAddress);
QDateTime lastSeen = QDateTime::fromMSecsSinceEpoch(m_cacheSettings->value("lastSeen").toLongLong());
// Load only the cache if we have the same format, otherwise we discard the cache and start over with the current version
// 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 (lastSeen.date().addDays(m_cacheCleanupPeriod) < now.date()) {
qCDebug(dcNetworkDeviceDiscovery()) << "Removing network device cache entry since it did not show up within the last" << m_cacheCleanupPeriod << "days" << mac.toString();
m_cacheSettings->remove("");
m_cacheSettings->endGroup(); // mac address
continue;
}
if (cacheVersion == CACHE_VERSION) {
m_cacheSettings->beginGroup("NetworkDeviceInfos");
foreach (const QString &addressString, m_cacheSettings->childGroups()) {
NetworkDeviceInfo info(mac.toString());
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()));
m_cacheSettings->beginGroup(addressString);
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();
m_networkInfoCache[mac] = info;
m_lastSeen[mac] = lastSeen;
} else {
qCWarning(dcNetworkDeviceDiscovery()) << "Clean up invalid cached network device info from cache" << info;
m_cacheSettings->remove("");
m_networkInfoCache.append(info);
m_lastSeen[info.address()] = lastSeen;
}
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.";
@ -530,17 +613,17 @@ void NetworkDeviceDiscoveryImpl::loadNetworkDeviceCache()
m_lastCacheHousekeeping = QDateTime::currentDateTime();
}
void NetworkDeviceDiscoveryImpl::removeFromNetworkDeviceCache(const MacAddress &macAddress)
void NetworkDeviceDiscoveryImpl::removeFromNetworkDeviceCache(const QHostAddress &address)
{
if (macAddress.isNull())
if (address.isNull())
return;
m_networkInfoCache.remove(macAddress);
m_lastSeen.remove(macAddress);
m_networkInfoCache.removeHostAddress(address);
m_lastSeen.remove(address);
m_cacheSettings->beginGroup("NetworkDeviceInfos");
m_cacheSettings->beginGroup(macAddress.toString());
m_cacheSettings->beginGroup(address.toString());
m_cacheSettings->remove("");
m_cacheSettings->endGroup(); // mac address
m_cacheSettings->endGroup(); // address
m_cacheSettings->endGroup(); // NetworkDeviceInfos
m_cacheSettings->sync();
}
@ -551,47 +634,70 @@ void NetworkDeviceDiscoveryImpl::saveNetworkDeviceCache(const NetworkDeviceInfo
return;
m_cacheSettings->beginGroup("NetworkDeviceInfos");
m_cacheSettings->beginGroup(deviceInfo.macAddress());
m_cacheSettings->setValue("address", deviceInfo.address().toString());
m_cacheSettings->beginGroup(deviceInfo.address().toString());
m_cacheSettings->setValue("hostName", deviceInfo.hostName());
m_cacheSettings->setValue("manufacturer", deviceInfo.macAddressManufacturer());
m_cacheSettings->setValue("interface", deviceInfo.networkInterface().name());
m_cacheSettings->setValue("lastSeen", convertMinuteBased(m_lastSeen.value(MacAddress(deviceInfo.macAddress()))).toMSecsSinceEpoch());
m_cacheSettings->endGroup(); // mac address
m_cacheSettings->setValue("lastSeen", convertMinuteBased(m_lastSeen.value(deviceInfo.address())).toMSecsSinceEpoch());
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->sync();
}
void NetworkDeviceDiscoveryImpl::updateCache(const NetworkDeviceInfo &deviceInfo)
{
MacAddress macAddress(deviceInfo.macAddress());
if (macAddress.isNull())
return;
if (m_monitors.contains(macAddress)) {
NetworkDeviceMonitorImpl *monitor = m_monitors.value(macAddress);
monitor->setNetworkDeviceInfo(deviceInfo);
// Update monitors
foreach (NetworkDeviceMonitorImpl *monitor, m_monitors.keys()) {
if (monitor->isMyNetworkDeviceInfo(deviceInfo)) {
monitor->setNetworkDeviceInfo(deviceInfo);
break;
}
}
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;
m_networkInfoCache[macAddress] = deviceInfo;
if (index < 0) {
m_networkInfoCache.append(deviceInfo);
} else {
m_networkInfoCache[index] = deviceInfo;
}
saveNetworkDeviceCache(deviceInfo);
}
void NetworkDeviceDiscoveryImpl::evaluateMonitor(NetworkDeviceMonitorImpl *monitor)
{
if (!monitor->networkDeviceInfo().isValid())
if (monitor->currentPingReply()) {
qCDebug(dcNetworkDeviceDiscovery()) << "Monitor has still a ping reply pending:" << monitor;
return;
}
if (monitor->currentPingReply())
return;
qCDebug(dcNetworkDeviceDiscovery()) << "Evaluate monitor" << monitor;
// Start action if we have not seen the device for gracePeriod seconds
QDateTime currentDateTime = QDateTime::currentDateTime();
bool requiresRefresh = false;
if (!monitor->networkDeviceInfo().isValid()) {
qCDebug(dcNetworkDeviceDiscovery()) << "Network device info not valid for" << monitor;
requiresRefresh = true;
}
if (monitor->lastSeen().isNull()) {
qCDebug(dcNetworkDeviceDiscovery()) << monitor << "requires refresh. Not seen since application start.";
requiresRefresh = true;
@ -618,53 +724,83 @@ void NetworkDeviceDiscoveryImpl::evaluateMonitor(NetworkDeviceMonitorImpl *monit
if (!requiresRefresh)
return;
// Try to ping first
qCDebug(dcNetworkDeviceDiscovery()) << monitor << "try to ping" << monitor->networkDeviceInfo().address().toString();
PingReply *reply = m_ping->ping(monitor->networkDeviceInfo().address(), monitor->pingRetries());
// Try to ping first.
// 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->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(retryCount)
monitor->setLastConnectionAttempt(QDateTime::currentDateTime());
});
connect(reply, &PingReply::destroyed, monitor, [=](){
monitor->setCurrentPingReply(nullptr);
connect(reply, &PingReply::destroyed, monitor, [monitor, reply](){
if (monitor->currentPingReply() == reply) {
monitor->setCurrentPingReply(nullptr);
}
});
connect(reply, &PingReply::finished, monitor, [=](){
processMonitorPingResult(reply, monitor);
connect(reply, &PingReply::finished, monitor, [monitor, reply](){
if (monitor->currentPingReply() == reply) {
monitor->setCurrentPingReply(nullptr);
}
});
}
void NetworkDeviceDiscoveryImpl::processArpTraffic(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress)
{
QDateTime now = QDateTime::currentDateTime();
m_lastSeen[macAddress] = now;
QDateTime currentDateTime = QDateTime::currentDateTime();
m_lastSeen[address] = currentDateTime;
if (m_networkInfoCache.contains(macAddress)) {
if (m_networkInfoCache.value(macAddress).address() != address) {
m_networkInfoCache[macAddress].setAddress(address);
saveNetworkDeviceCache(m_networkInfoCache.value(macAddress));
}
}
// Update monitors and cache
for (int i = 0; i < m_networkInfoCache.count(); i++) {
switch (m_networkInfoCache.at(i).monitorMode()) {
case NetworkDeviceInfo::MonitorModeMac:
if (m_networkInfoCache.at(i).macAddressInfos().hasMacAddress(macAddress) &&
m_networkInfoCache.at(i).address() != address) {
// Update the monitors
NetworkDeviceMonitorImpl *monitor = m_monitors.value(macAddress);
if (monitor) {
monitor->setLastSeen(now);
if (monitor->networkDeviceInfo().address() != address) {
NetworkDeviceInfo info = monitor->networkDeviceInfo();
info.setAddress(address);
monitor->setNetworkDeviceInfo(info);
qCDebug(dcNetworkDeviceDiscovery()) << "NetworkDeviceMonitor" << monitor << "ip address changed";
emit monitor->networkDeviceInfoChanged(monitor->networkDeviceInfo());
}
QHostAddress oldAddress = m_networkInfoCache.at(i).address();
qCDebug(dcNetworkDeviceDiscovery()) << "Host" << macAddress.toString() << "changed the IP address from"
<< oldAddress.toString()
<< "-->"
<< address.toString();
if (monitor->networkDeviceInfo().isComplete()) {
monitor->setReachable(true);
removeFromNetworkDeviceCache(oldAddress);
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
bool requiresMacAddressLookup = true;
if (m_networkInfoCache.contains(macAddress)) {
QString cachedManufacturer = m_networkInfoCache[macAddress].macAddressManufacturer();
if (m_macVendorCache.contains(macAddress)) {
QString cachedManufacturer = m_macVendorCache.value(macAddress);
if (!cachedManufacturer.isEmpty()) {
// Found the mac address manufacturer in the cache, let's use that one...
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)
qCDebug(dcNetworkDeviceDiscovery()) << "MAC manufacturer lookup finished for" << macAddress << ":" << reply->manufacturer();
if (!reply->manufacturer().isEmpty())
m_macVendorCache.insert(macAddress, reply->manufacturer());
if (m_currentDiscoveryReply) {
m_currentDiscoveryReply->processMacManufacturer(macAddress, reply->manufacturer());
@ -739,6 +878,58 @@ QDateTime NetworkDeviceDiscoveryImpl::convertMinuteBased(const QDateTime &dateTi
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)
{
// Ignore ARP from zero mac
@ -766,7 +957,7 @@ void NetworkDeviceDiscoveryImpl::onArpRequstReceived(const QNetworkInterface &in
void NetworkDeviceDiscoveryImpl::evaluateMonitors()
{
bool monitorRequiresRediscovery = false;
foreach (NetworkDeviceMonitorImpl *monitor, m_monitors) {
foreach (NetworkDeviceMonitorImpl *monitor, m_monitors.keys()) {
evaluateMonitor(monitor);
// 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);
}
// Do some cache housekeeping if required
if (m_lastCacheHousekeeping.addDays(1) < QDateTime::currentDateTime()) {
qCInfo(dcNetworkDeviceDiscovery()) << "Starting cache housekeeping since it is more than one day since the last clanup...";
QDateTime now = QDateTime::currentDateTime();
foreach (const MacAddress &mac, m_lastSeen.keys()) {
// Remove the info from the cache if not seen fo the last 30 days...
if (m_lastSeen.value(mac).date().addDays(m_cacheCleanupPeriod) < QDateTime::currentDateTime().date()) {
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;
}
// FIXME
// // Do some cache housekeeping if required
// if (m_lastCacheHousekeeping.addDays(1) < QDateTime::currentDateTime()) {
// qCInfo(dcNetworkDeviceDiscovery()) << "Starting cache housekeeping since it is more than one day since the last clanup...";
// QDateTime currentDateTime = QDateTime::currentDateTime();
// foreach (const MacAddress &mac, m_lastSeen.keys()) {
// // Remove the info from the cache if not seen fo the last 30 days...
// if (m_lastSeen.value(mac).date().addDays(m_cacheCleanupPeriod) < QDateTime::currentDateTime().date()) {
// 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 = currentDateTime;
// }
}
void NetworkDeviceDiscoveryImpl::finishDiscovery()

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2022, nymea GmbH
* Copyright 2013 - 2024, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
@ -31,10 +31,11 @@
#ifndef NETWORKDEVICEDISCOVERYIMPL_H
#define NETWORKDEVICEDISCOVERYIMPL_H
#include <QHash>
#include <QObject>
#include <QSettings>
#include <QLoggingCategory>
#include <QDateTime>
#include <QLoggingCategory>
#include <network/networkdeviceinfo.h>
#include <network/networkdevicediscovery.h>
@ -66,12 +67,11 @@ public:
NetworkDeviceDiscoveryReply *discover() override;
NetworkDeviceMonitor *registerMonitor(const MacAddress &macAddress) override;
void unregisterMonitor(const MacAddress &macAddress) override;
NetworkDeviceMonitor *registerMonitor(Thing *thing) override;
void unregisterMonitor(NetworkDeviceMonitor *networkDeviceMonitor) 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);
MacAddressDatabaseReply *lookupMacAddress(const QString &macAddress) override;
@ -79,7 +79,7 @@ public:
bool sendArpRequest(const QHostAddress &address) override;
QHash<MacAddress, NetworkDeviceInfo> cache() const override;
NetworkDeviceInfos cache() const override;
protected:
void setEnabled(bool enabled) override;
@ -94,6 +94,7 @@ private:
MacAddressDatabase *m_macAddressDatabase = nullptr;
ArpSocket *m_arpSocket = nullptr;
Ping *m_ping = nullptr;
bool m_enabled = true;
bool m_running = false;
@ -112,12 +113,15 @@ private:
QList<MacAddressDatabaseReply *> m_runningMacDatabaseReplies;
QList<PingReply *> m_runningPingReplies;
QHash<MacAddress, NetworkDeviceMonitorImpl *> m_monitors;
QHash<MacAddress, int> m_monitorsReferenceCount;
QHash<MacAddress, QDateTime> m_lastSeen;
QHash<NetworkDeviceMonitorImpl *, QVector<NetworkDeviceMonitorImpl *>> m_monitors;
QHash<QHostAddress, QDateTime> m_lastSeen;
QHash<MacAddress, QString> m_macVendorCache;
QHash<QHostAddress, NetworkDeviceInfo> m_infos;
QSettings *m_cacheSettings;
QHash<MacAddress, NetworkDeviceInfo> m_networkInfoCache;
NetworkDeviceInfos m_networkInfoCache;
void pingAllNetworkDevices();
@ -127,6 +131,7 @@ private:
void loadNetworkDeviceCache();
void removeFromNetworkDeviceCache(const MacAddress &macAddress);
void removeFromNetworkDeviceCache(const QHostAddress &address);
void saveNetworkDeviceCache(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 testPingMonitor(NetworkDeviceMonitorImpl *monitor);
// Time helpers
bool longerAgoThan(const QDateTime &dateTime, uint minutes);
QDateTime convertMinuteBased(const QDateTime &dateTime = QDateTime());
NetworkDeviceMonitorImpl *createPluginMonitor(NetworkDeviceMonitorImpl *internalMonitor);
void cleanupPluginMonitor(NetworkDeviceMonitorImpl *pluginMonitor);
private slots:
void onArpResponseReceived(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 "network/networkutils.h"
#include "loggingcategories.h"
#include <QDateTime>
@ -48,11 +49,6 @@ NetworkDeviceInfos NetworkDeviceDiscoveryReplyImpl::networkDeviceInfos() const
return m_networkDeviceInfos;
}
NetworkDeviceInfos NetworkDeviceDiscoveryReplyImpl::virtualNetworkDeviceInfos() const
{
return m_virtualNetworkDeviceInfos;
}
bool NetworkDeviceDiscoveryReplyImpl::isFinished() const
{
return m_isFinished;
@ -65,49 +61,34 @@ void NetworkDeviceDiscoveryReplyImpl::setFinished(bool finished)
void NetworkDeviceDiscoveryReplyImpl::processPingResponse(const QHostAddress &address, const QString &hostName)
{
foreach (const NetworkDeviceInfo &info, m_networkDeviceCache) {
if (info.address() == address) {
// Already found info, set host name and check if complete
MacAddress macAddress(info.macAddress());
m_networkDeviceCache[macAddress].setHostName(hostName);
verifyComplete(macAddress);
return;
}
if (m_networkDeviceCache.contains(address)) {
m_networkDeviceCache[address].setHostName(hostName);
} else {
NetworkDeviceInfo info;
info.setAddress(address);
info.setHostName(hostName);
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)
{
if (m_pingCache.contains(address)) {
// We know this device from a ping response
NetworkDeviceInfo info = m_pingCache.take(address);
if (m_networkDeviceCache.contains(address)) {
m_networkDeviceCache[address].addMacAddress(macAddress);
m_networkDeviceCache[address].setNetworkInterface(interface);
} else {
NetworkDeviceInfo info(macAddress.toString());
info.setAddress(address);
info.setNetworkInterface(interface);
info.setMacAddress(macAddress.toString());
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);
}
}
m_networkDeviceCache[address] = info;
verifyComplete(macAddress);
// First time seeing this host address
emit hostAddressDiscovered(address);
}
}
void NetworkDeviceDiscoveryReplyImpl::processMacManufacturer(const MacAddress &macAddress, const QString &manufacturer)
@ -115,45 +96,37 @@ void NetworkDeviceDiscoveryReplyImpl::processMacManufacturer(const MacAddress &m
if (macAddress.isNull())
return;
if (m_networkDeviceCache.contains(macAddress)) {
m_networkDeviceCache[macAddress].setMacAddressManufacturer(manufacturer);
} else {
NetworkDeviceInfo info(macAddress.toString());
info.setMacAddressManufacturer(manufacturer);
m_networkDeviceCache[macAddress] = info;
foreach (const NetworkDeviceInfo &info, m_networkDeviceCache) {
if (info.macAddressInfos().hasMacAddress(macAddress)) {
m_networkDeviceCache[info.address()].addMacAddress(macAddress, manufacturer);
}
}
verifyComplete(macAddress);
}
void NetworkDeviceDiscoveryReplyImpl::processDiscoveryFinished()
{
// Lets see if we have any incomplete infos but enougth data to be shown
foreach (const MacAddress &macAddress, m_networkDeviceCache.keys()) {
// If already in the result, ignore it
if (m_networkDeviceInfos.hasMacAddress(macAddress))
continue;
// Add the discovery cache to the final result
foreach (const QHostAddress &address, m_networkDeviceCache.keys()) {
NetworkDeviceInfo info = m_networkDeviceCache.value(macAddress);
MacAddress infoMacAddress(info.macAddress());
if (m_networkDeviceCache.value(address).macAddressInfos().isEmpty() && !m_networkDeviceCache.value(address).networkInterface().isValid()) {
// 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
<< "Valid:" << info.isValid()
<< "Complete:" << info.isComplete()
<< info.incompleteProperties();
// We need at least a valid mac address and a valid ip address, the rest ist pure informative
if (infoMacAddress == macAddress && !infoMacAddress.isNull() && !info.address().isNull()) {
qCDebug(dcNetworkDeviceDiscovery()) << "Adding incomplete" << info << "to the final result:" << info.incompleteProperties();
// 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);
}
qCDebug(dcNetworkDeviceDiscovery()) << "Adding incomplete" << info << "to the final result:" << info.incompleteProperties();
m_networkDeviceCache[address].forceComplete();
m_networkDeviceInfos.append(m_networkDeviceCache.take(address));
}
// Evaluate overall monitor mode...
evaluateMonitorMode();
// Done, lets sort the result and inform
m_networkDeviceInfos.sortNetworkDevices();
@ -165,88 +138,87 @@ void NetworkDeviceDiscoveryReplyImpl::processDiscoveryFinished()
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;
emit finished();
}
QHash<QHostAddress, NetworkDeviceInfo> NetworkDeviceDiscoveryReplyImpl::currentCache() const
{
return m_networkDeviceCache;
}
void NetworkDeviceDiscoveryReplyImpl::addCompleteNetworkDeviceInfo(const NetworkDeviceInfo &networkDeviceInfo)
{
// Note: this method will be called only from the discovery
if (!m_networkDeviceInfos.hasMacAddress(networkDeviceInfo.macAddress())) {
if (!m_networkDeviceInfos.hasHostAddress(networkDeviceInfo.address()))
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
if (!m_networkDeviceInfos.hasHostAddress(networkDeviceInfo.address())) {
m_virtualNetworkDeviceInfos.append(networkDeviceInfo);
}
}
for (int i = 0; i < m_networkDeviceInfos.size(); i++) {
QString NetworkDeviceDiscoveryReplyImpl::macAddressFromHostAddress(const QHostAddress &address)
{
foreach (const NetworkDeviceInfo &info, m_networkDeviceCache) {
if (info.address() == address) {
return info.macAddress();
}
}
const NetworkDeviceInfo info = m_networkDeviceInfos.at(i);
qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: Evaluating host" << info.address().toString();
return QString();
}
NetworkDeviceInfo::MonitorMode mode = NetworkDeviceInfo::MonitorModeMac;
bool NetworkDeviceDiscoveryReplyImpl::hasHostAddress(const QHostAddress &address)
{
return ! macAddressFromHostAddress(address).isEmpty();
}
if (info.macAddressInfos().isEmpty()) {
void NetworkDeviceDiscoveryReplyImpl::verifyComplete(const MacAddress &macAddress)
{
if (!m_networkDeviceCache.contains(macAddress))
return;
if (m_networkDeviceCache[macAddress].isComplete() && m_networkDeviceCache[macAddress].isValid()) {
if (m_networkDeviceInfos.hasMacAddress(macAddress)) {
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);
qCDebug(dcNetworkDeviceDiscovery()) << "--> Ignoring" << m_networkDeviceCache.value(macAddress);
// No MAC address found, no ARP for this host, probably a VPN client
if (info.hostName().isEmpty()) {
qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: --> No MAC address and no host name, using MonitorModeIp";
mode = NetworkDeviceInfo::MonitorModeIp;
} else {
qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: --> No MAC address, but we have a host name, suing MonitorModeHostName";
mode = NetworkDeviceInfo::MonitorModeHostName;
}
} else if (info.macAddressInfos().size() == 1) {
// 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;
NetworkDeviceInfos networkDeviceInfos() const override;
NetworkDeviceInfos virtualNetworkDeviceInfos() const override;
bool isFinished() const override;
void setFinished(bool finished);
@ -60,28 +59,19 @@ public:
void processDiscoveryFinished();
QHash<QHostAddress, NetworkDeviceInfo> currentCache() const;
public slots:
void addCompleteNetworkDeviceInfo(const NetworkDeviceInfo &networkDeviceInfo);
void addVirtualNetworkDeviceInfo(const NetworkDeviceInfo &networkDeviceInfo);
private:
NetworkDeviceInfos m_networkDeviceInfos; // Contains only complete and valid infos
NetworkDeviceInfos m_virtualNetworkDeviceInfos; // Contains ping responses without ARP, like VPN devices
QHash<MacAddress, NetworkDeviceInfo> m_networkDeviceCache;
QHash<QHostAddress, NetworkDeviceInfo> m_networkDeviceCache;
qint64 m_startTimestamp;
bool m_isFinished = false;
// Temporary cache for ping responses where the mac is not known yet (like VPN devices)
QHash<QHostAddress, NetworkDeviceInfo> m_pingCache;
QString macAddressFromHostAddress(const QHostAddress &address);
bool hasHostAddress(const QHostAddress &address);
void verifyComplete(const MacAddress &macAddress);
NetworkDeviceInfos m_networkDeviceInfos;
void evaluateMonitorMode();
};
}

View File

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

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, nymea GmbH
* Copyright 2013 - 2024, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
@ -202,9 +202,9 @@ void PluginMetadata::parse(const QJsonObject &jsonObject)
QJsonObject thingClassObject = thingClassJson.toObject();
/*! Returns a list of all valid JSON properties a ThingClass JSON definition can have. */
QStringList thingClassProperties = QStringList() << "id" << "name" << "displayName" << "createMethods" << "setupMethod"
<< "interfaces" << "providedInterfaces" << "browsable" << "discoveryParamTypes"
<< "paramTypes" << "settingsTypes" << "stateTypes" << "actionTypes" << "eventTypes" << "browserItemActionTypes"
<< "discoveryType";
<< "interfaces" << "providedInterfaces" << "browsable" << "discoveryParamTypes"
<< "paramTypes" << "settingsTypes" << "stateTypes" << "actionTypes" << "eventTypes" << "browserItemActionTypes"
<< "discoveryType";
QStringList mandatoryThingClassProperties = QStringList() << "id" << "name" << "displayName";
QPair<QStringList, QStringList> verificationResult = verifyFields(thingClassProperties, mandatoryThingClassProperties, thingClassObject);
@ -703,6 +703,64 @@ void PluginMetadata::parse(const QJsonObject &jsonObject)
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()) {
if (!stateTypes.contains(ifaceStateType.name())) {
if (!ifaceStateType.optional()) {

View File

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

View File

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

View File

@ -189,9 +189,39 @@ Interface ThingUtils::loadInterface(const QString &name)
}
}
InterfaceParamTypes paramTypes;
InterfaceStateTypes stateTypes;
InterfaceActionTypes actionTypes;
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()) {
QVariantMap stateMap = stateVariant.toMap();
InterfaceStateType stateType;
@ -279,11 +309,17 @@ Interface ThingUtils::loadInterface(const QString &name)
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)
{
InterfaceParamTypes paramTypes = iface1.paramTypes();
foreach (const InterfaceParamType &pt, iface2.paramTypes()) {
if (paramTypes.findByName(pt.name()).name().isEmpty()) {
paramTypes.append(pt);
}
}
InterfaceEventTypes eventTypes = iface1.eventTypes();
foreach (const InterfaceEventType &et, iface2.eventTypes()) {
if (eventTypes.findByName(et.name()).name().isEmpty()) {
@ -302,7 +338,7 @@ Interface ThingUtils::mergeInterfaces(const Interface &iface1, const Interface &
actionTypes.append(at);
}
}
return Interface(QString(), actionTypes, eventTypes, stateTypes);
return Interface(QString(), paramTypes, actionTypes, eventTypes, stateTypes);
}
QStringList ThingUtils::generateInterfaceParentList(const QString &interface)

View File

@ -100,5 +100,6 @@
<file>alarm.json</file>
<file>firesensor.json</file>
<file>childlock.json</file>
<file>networkdevice.json</file>
</qresource>
</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/macaddress.h \
network/macaddressdatabasereply.h \
network/macaddressinfo.h \
network/macaddressinfos.h \
network/networkdevicediscovery.h \
network/networkdevicediscoveryreply.h \
network/networkdeviceinfo.h \
@ -72,6 +74,7 @@ HEADERS += \
types/browseraction.h \
types/interfaceactiontype.h \
types/interfaceeventtype.h \
types/interfaceparamtype.h \
types/interfacestatetype.h \
types/mediabrowseritem.h \
types/thingclass.h \
@ -174,6 +177,8 @@ SOURCES += \
network/arpsocket.cpp \
network/macaddress.cpp \
network/macaddressdatabasereply.cpp \
network/macaddressinfo.cpp \
network/macaddressinfos.cpp \
network/networkdevicediscovery.cpp \
network/networkdeviceinfo.cpp \
network/networkdeviceinfos.cpp \
@ -214,6 +219,7 @@ SOURCES += \
types/browseraction.cpp \
types/interfaceactiontype.cpp \
types/interfaceeventtype.cpp \
types/interfaceparamtype.cpp \
types/interfacestatetype.cpp \
types/mediabrowseritem.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 "networkdevicediscoveryreply.h"
#include "integrations/thing.h"
class LIBNYMEA_EXPORT NetworkDeviceDiscovery : public HardwareResource
{
Q_OBJECT
@ -55,19 +57,18 @@ public:
virtual bool running() const = 0;
virtual NetworkDeviceMonitor *registerMonitor(const MacAddress &macAddress) = 0;
virtual void unregisterMonitor(const MacAddress &macAddress) = 0;
virtual NetworkDeviceMonitor *registerMonitor(Thing *thing) = 0;
virtual void unregisterMonitor(NetworkDeviceMonitor *networkDeviceMonitor) = 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 MacAddress &macAddress) = 0;
virtual bool sendArpRequest(const QHostAddress &address) = 0;
virtual QHash<MacAddress, NetworkDeviceInfo> cache() const = 0;
virtual NetworkDeviceInfos cache() const = 0;
signals:
void runningChanged(bool running);

View File

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

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2022, nymea GmbH
* Copyright 2013 - 2024, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
@ -36,38 +36,16 @@ NetworkDeviceInfo::NetworkDeviceInfo()
}
NetworkDeviceInfo::NetworkDeviceInfo(const QString &macAddress):
m_macAddress(macAddress)
NetworkDeviceInfo::NetworkDeviceInfo(const QString &macAddress)
{
m_macAddressSet = true;
addMacAddress(MacAddress(macAddress));
}
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
@ -92,6 +70,33 @@ void NetworkDeviceInfo::setHostName(const QString &hostName)
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
{
return m_networkInterface;
@ -103,34 +108,102 @@ void NetworkDeviceInfo::setNetworkInterface(const QNetworkInterface &networkInte
m_networkInterfaceSet = true;
}
NetworkDeviceInfo::MonitorMode NetworkDeviceInfo::monitorMode() const
{
return m_monitorMode;
}
void NetworkDeviceInfo::setMonitorMode(MonitorMode monitorMode)
{
m_monitorMode = monitorMode;
}
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
{
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
{
QStringList list;
if (!m_macAddressSet) list.append("MAC not set");
if (!m_macAddressManufacturerSet) list.append("MAC vendor not set");
if (!m_hostNameSet) list.append("hostname not set");
if (!m_networkInterfaceSet) list.append("nework interface not set");
if (m_macAddressInfos.isEmpty())
list.append("MAC address 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(", ");
}
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
{
return MacAddress(m_macAddress) == MacAddress(other.macAddress()) &&
m_address == other.address() &&
m_hostName == other.hostName() &&
m_macAddressManufacturer == other.macAddressManufacturer() &&
m_networkInterface.name() == other.networkInterface().name() &&
isComplete() == other.isComplete();
return m_address == other.address() &&
m_macAddressInfos == other.macAddressInfos() &&
m_hostName == other.hostName() &&
m_networkInterface.name() == other.networkInterface().name() &&
m_monitorMode == other.monitorMode() &&
isComplete() == other.isComplete();
}
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)
{
QDebugStateSaver saver(dbg);
dbg.nospace() << "NetworkDeviceInfo(" << networkDeviceInfo.address().toString();
dbg.nospace().noquote() << "NetworkDeviceInfo(" << networkDeviceInfo.address().toString();
if (!networkDeviceInfo.macAddress().isEmpty())
dbg.nospace() << ", " << MacAddress(networkDeviceInfo.macAddress()).toString();
dbg.nospace().noquote() << ", Monitor mode: ";
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())
dbg.nospace() << " (" << networkDeviceInfo.macAddressManufacturer() << ") ";
foreach (const MacAddressInfo &macInfo, networkDeviceInfo.macAddressInfos())
dbg.nospace().noquote() << ", " << macInfo;
if (!networkDeviceInfo.hostName().isEmpty())
dbg.nospace() << ", hostname: " << networkDeviceInfo.hostName();
dbg.nospace().noquote() << ", hostname: " << networkDeviceInfo.hostName();
if (networkDeviceInfo.networkInterface().isValid())
dbg.nospace() << ", " << networkDeviceInfo.networkInterface().name();
dbg.nospace().noquote() << ", " << networkDeviceInfo.networkInterface().name();
dbg.nospace() << ")";
dbg.nospace().noquote() << ")";
return dbg;
}

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2022, nymea GmbH
* Copyright 2013 - 2024, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
@ -38,53 +38,70 @@
#include <QNetworkInterface>
#include "libnymea.h"
#include "macaddressinfos.h"
class LIBNYMEA_EXPORT NetworkDeviceInfo
{
Q_GADGET
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(const QString &macAddress);
explicit NetworkDeviceInfo(const QHostAddress &address);
QString macAddress() const;
void setMacAddress(const QString &macAddress);
QString macAddressManufacturer() const;
void setMacAddressManufacturer(const QString &macAddressManufacturer);
QHostAddress address() const;
void setAddress(const QHostAddress &address);
QString hostName() const;
void setHostName(const QString &hostName);
MacAddressInfos macAddressInfos() const;
void addMacAddress(const MacAddress &macAddress);
void addMacAddress(const MacAddress &macAddress, const QString &vendorName);
QNetworkInterface networkInterface() const;
void setNetworkInterface(const QNetworkInterface &networkInterface);
MonitorMode monitorMode() const;
void setMonitorMode(MonitorMode monitorMode);
bool isValid() const;
bool isComplete() const;
void forceComplete();
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;
private:
QHostAddress m_address;
QString m_macAddress;
QString m_macAddressManufacturer;
MacAddressInfos m_macAddressInfos;
QString m_hostName;
QNetworkInterface m_networkInterface;
MonitorMode m_monitorMode = MonitorModeMac;
bool m_macAddressSet = false;
bool m_macAddressManufacturerSet = false;
bool m_addressSet = false;
bool m_hostNameSet = false;
bool m_networkInterfaceSet = false;
bool m_forceComplete = false;
};
QDebug operator<<(QDebug debug, const NetworkDeviceInfo &networkDeviceInfo);
#endif // NETWORKDEVICEINFO_H

View File

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

View File

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

View File

@ -40,12 +40,20 @@ NetworkDeviceMonitor::NetworkDeviceMonitor(QObject *parent) :
QDebug operator<<(QDebug dbg, NetworkDeviceMonitor *networkDeviceMonitor)
{
QDebugStateSaver saver(dbg);
dbg.nospace() << "NetworkDeviceMonitor(" << networkDeviceMonitor->macAddress().toString();
dbg.nospace() << "NetworkDeviceMonitor(";
if (!networkDeviceMonitor->networkDeviceInfo().macAddressManufacturer().isEmpty())
dbg.nospace() << " - " << networkDeviceMonitor->networkDeviceInfo().macAddressManufacturer();
switch (networkDeviceMonitor->monitorMode()) {
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() << ")";
return dbg;

View File

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

View File

@ -141,6 +141,18 @@ PingReply *Ping::ping(const QHostAddress &hostAddress, bool lookupHost, uint ret
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()
{
if (m_queueTimer->isActive())
@ -150,7 +162,7 @@ void Ping::sendNextReply()
return;
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();
QTimer::singleShot(0, this, [=]() {
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]"
<< "ID:" << QString("0x%1").arg(reply->requestId(), 4, 16, QChar('0'))
<< "Sequence:" << reply->sequenceNumber();
<< "ID:" << QString("0x%1").arg(reply->requestId(), 4, 16, QChar('0'))
<< "Sequence:" << reply->sequenceNumber();
// Send packet to the target ip
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;
}
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)
{
// Check if we should retry
if (reply->m_retryCount >= reply->m_retries ||
error == PingReply::ErrorNoError ||
error == PingReply::ErrorAborted ||
error == PingReply::ErrorInvalidHostAddress ||
error == PingReply::ErrorPermissionDenied) {
error == PingReply::ErrorNoError ||
error == PingReply::ErrorAborted ||
error == PingReply::ErrorInvalidHostAddress ||
error == PingReply::ErrorPermissionDenied||
error == PingReply::ErrorHostNameLookupFailed ||
error == PingReply::ErrorHostNameNotFound) {
// No retry, we are done
reply->m_error = error;
reply->m_timer->stop();
@ -374,11 +415,11 @@ void Ping::cleanUpReply(PingReply *reply)
m_pendingReplies.remove(reply->requestId());
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
int lookupId = m_pendingHostLookups.key(reply);
int lookupId = m_pendingHostNameLookups.key(reply);
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));
qCDebug(dcPingTraffic()) << "IP header: Lenght" << ipHeaderLength
<< "Sender:" << senderAddress.toString()
<< "Destination:" << destinationAddress.toString()
<< "Size:" << htons(ipHeader->tot_len) << "B"
<< "TTL" << ipHeader->ttl;
<< "Sender:" << senderAddress.toString()
<< "Destination:" << destinationAddress.toString()
<< "Size:" << htons(ipHeader->tot_len) << "B"
<< "TTL" << ipHeader->ttl;
struct icmp *responsePacket = reinterpret_cast<struct icmp *>(receiveBuffer + ipHeaderLength);
quint16 icmpId = htons(responsePacket->icmp_id);
quint16 icmpSequnceNumber = htons(responsePacket->icmp_seq);
qCDebug(dcPingTraffic()) << "ICMP packt (Size:" << icmpPacketSize << "Bytes):"
<< "Type" << responsePacket->icmp_type
<< "Code:" << responsePacket->icmp_code
<< "ID:" << QString("0x%1").arg(icmpId, 4, 16, QChar('0'))
<< "Sequence:" << icmpSequnceNumber;
<< "Type" << responsePacket->icmp_type
<< "Code:" << responsePacket->icmp_code
<< "ID:" << QString("0x%1").arg(icmpId, 4, 16, QChar('0'))
<< "Sequence:" << icmpSequnceNumber;
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;
qCDebug(dcPing()) << "Received ICMP response" << reply->targetHostAddress().toString() << ICMP_PACKET_SIZE << "[Bytes]"
<< "ID:" << QString("0x%1").arg(icmpId, 4, 16, QChar('0'))
<< "Sequence:" << icmpSequnceNumber
<< "Time:" << reply->duration() << "[ms]";
<< "ID:" << QString("0x%1").arg(icmpId, 4, 16, QChar('0'))
<< "Sequence:" << icmpSequnceNumber
<< "Time:" << reply->duration() << "[ms]";
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
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
} else {
finishReply(reply, PingReply::ErrorNoError);
@ -472,20 +513,20 @@ void Ping::onSocketReadyRead(int socketDescriptor)
QHostAddress nestedDestinationAddress(qFromBigEndian(nestedIpHeader->daddr));
qCDebug(dcPingTraffic()) << "++ IP header: Lenght" << nestedIpHeaderLength
<< "Sender:" << nestedSenderAddress.toString()
<< "Destination:" << nestedDestinationAddress.toString()
<< "Size:" << htons(nestedIpHeader->tot_len) << "B"
<< "TTL" << ipHeader->ttl;
<< "Sender:" << nestedSenderAddress.toString()
<< "Destination:" << nestedDestinationAddress.toString()
<< "Size:" << htons(nestedIpHeader->tot_len) << "B"
<< "TTL" << ipHeader->ttl;
struct icmp *nestedResponsePacket = reinterpret_cast<struct icmp *>(receiveBuffer + messageOffset + nestedIpHeaderLength);
icmpId = htons(nestedResponsePacket->icmp_id);
icmpSequnceNumber = htons(nestedResponsePacket->icmp_seq);
qCDebug(dcPingTraffic()) << "++ ICMP packt (Size:" << nestedIcmpPacketSize << "Bytes):"
<< "Type" << nestedResponsePacket->icmp_type
<< "Code:" << nestedResponsePacket->icmp_code
<< "ID:" << QString("0x%1").arg(icmpId, 4, 16, QChar('0'))
<< "Sequence:" << icmpSequnceNumber;
<< "Type" << nestedResponsePacket->icmp_type
<< "Code:" << nestedResponsePacket->icmp_code
<< "ID:" << QString("0x%1").arg(icmpId, 4, 16, QChar('0'))
<< "Sequence:" << icmpSequnceNumber;
qCDebug(dcPing()) << "ICMP destination unreachable" << nestedDestinationAddress.toString()
<< "Code:" << nestedResponsePacket->icmp_code
@ -495,9 +536,9 @@ void Ping::onSocketReadyRead(int socketDescriptor)
PingReply *reply = m_pendingReplies.value(icmpId);
if (!reply) {
qCDebug(dcPingTraffic()) << "No pending reply for ping echo response unreachable with ID"
<< QString("0x%1").arg(icmpId, 4, 16, QChar('0'))
<< "Sequence:" << icmpSequnceNumber
<< "from" << nestedSenderAddress.toString() << "to" << nestedDestinationAddress.toString();
<< QString("0x%1").arg(icmpId, 4, 16, QChar('0'))
<< "Sequence:" << icmpSequnceNumber
<< "from" << nestedSenderAddress.toString() << "to" << nestedDestinationAddress.toString();
return;
}
@ -508,20 +549,74 @@ void Ping::onSocketReadyRead(int socketDescriptor)
void Ping::onHostLookupFinished(const QHostInfo &info)
{
PingReply *reply = m_pendingHostLookups.value(info.lookupId());
if (!reply) {
qCWarning(dcPing()) << "Could not find reply after host lookup.";
return;
}
if (m_pendingHostNameLookups.contains(info.lookupId())) {
if (info.error() != QHostInfo::NoError) {
qCWarning(dcPing()) << "Failed to look up hostname after successfull ping" << reply->targetHostAddress().toString() << info.error();
} else {
qCDebug(dcPing()) << "Looked up hostname after successfull ping" << reply->targetHostAddress().toString() << info.hostName();
if (info.hostName() != reply->targetHostAddress().toString()) {
reply->m_hostName = info.hostName();
PingReply *reply = m_pendingHostNameLookups.take(info.lookupId());
if (!reply) {
qCWarning(dcPing()) << "Could not find ping reply after finished host lookup" << info.hostName() << info.addresses() << info.errorString();
return;
}
}
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, bool lookupHost, uint retries = 3);
PingReply *ping(const QString &hostName, uint retries = 3);
signals:
void availableChanged(bool available);
@ -91,7 +93,8 @@ private:
QTimer *m_queueTimer = nullptr;
PingReply *m_currentReply = nullptr;
void sendNextReply();
QHash<int, PingReply *> m_pendingHostLookups;
QHash<int, PingReply *> m_pendingHostNameLookups;
QHash<int, PingReply *> m_pendingHostAddressLookups;
//Error performPing(const QString &address);
void performPing(PingReply *reply);
@ -104,13 +107,13 @@ private:
quint16 calculateRequestId();
PingReply *createReply(const QHostAddress &hostAddress);
PingReply *createReply(const QString &hostName);
void finishReply(PingReply *reply, PingReply::Error error);
void cleanUpReply(PingReply *reply);
private slots:
void onSocketReadyRead(int socketDescriptor);
void onHostLookupFinished(const QHostInfo &info);
};
#endif // PING_H

View File

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

View File

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

View File

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

View File

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

View File

@ -13,7 +13,7 @@ isEmpty(NYMEA_VERSION) {
JSON_PROTOCOL_VERSION_MAJOR=8
JSON_PROTOCOL_VERSION_MINOR=2
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_PATCH=0
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 ActionTypeId virtualIoTemperatureSensorMockInputActionTypeId;
extern ParamTypeId virtualIoTemperatureSensorMockInputActionInputParamTypeId;
extern ThingClassId networkDeviceMockThingClassId;
extern ParamTypeId networkDeviceMockThingMacAddressParamTypeId;
extern ParamTypeId networkDeviceMockThingHostNameParamTypeId;
extern ParamTypeId networkDeviceMockThingAddressParamTypeId;
extern ParamTypeId networkDeviceMockDiscoveryResultTypeParamTypeId;
#endif // EXTERNPLUGININFO_H

View File

@ -126,6 +126,27 @@ void IntegrationPluginMock::discoverThings(ThingDiscoveryInfo *info)
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();
info->finish(Thing::ThingErrorThingNotFound);
}
@ -273,6 +294,12 @@ void IntegrationPluginMock::setupThing(ThingSetupInfo *info)
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();
info->finish(Thing::ThingErrorThingClassNotFound);
}

View File

@ -1198,6 +1198,59 @@
"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 <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_LOGGING_CATEGORY(dcMock, "Mock")
@ -236,6 +236,11 @@ StateTypeId virtualIoTemperatureSensorMockInputStateTypeId = StateTypeId("{fd341
StateTypeId virtualIoTemperatureSensorMockTemperatureStateTypeId = StateTypeId("{db9cc518-1012-47e2-8212-6e616fed07a6}");
ActionTypeId virtualIoTemperatureSensorMockInputActionTypeId = ActionTypeId("{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[] {
//: 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})
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})
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
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})
QT_TRANSLATE_NOOP("mock", "Mac address"),
@ -481,6 +495,9 @@ const QString translations[] {
//: The name of the ThingClass ({6fe07a77-9c07-4736-81e2-d504314bbcb9})
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})
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})
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})
QT_TRANSLATE_NOOP("mock", "Search text"),

View File

@ -298,8 +298,8 @@ void TestIntegrations::getThingClasses_data()
QTest::addColumn<QList<ThingClassId>>("thingClassIds");
QTest::addColumn<int>("resultCount");
QTest::newRow("vendor nymea") << nymeaVendorId << QList<ThingClassId>() << 16;
QTest::newRow("no filter") << VendorId() << QList<ThingClassId>() << 16;
QTest::newRow("vendor nymea") << nymeaVendorId << QList<ThingClassId>() << 17;
QTest::newRow("no filter") << VendorId() << QList<ThingClassId>() << 17;
QTest::newRow("invalid vendor") << VendorId("93e7d361-8025-4354-b17e-b68406c800bc") << QList<ThingClassId>() << 0;
QTest::newRow("mockThingClassId") << VendorId() << (QList<ThingClassId>() << mockThingClassId) << 1;
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/actiontype.cpp \
$$top_srcdir/libnymea/types/interface.cpp \
$$top_srcdir/libnymea/types/interfaceparamtype.cpp \
$$top_srcdir/libnymea/types/interfacestatetype.cpp \
$$top_srcdir/libnymea/types/interfaceeventtype.cpp \
$$top_srcdir/libnymea/types/interfaceactiontype.cpp \
@ -42,6 +43,7 @@ HEADERS += \
$$top_srcdir/libnymea/types/eventtype.h \
$$top_srcdir/libnymea/types/actiontype.h \
$$top_srcdir/libnymea/types/interface.h \
$$top_srcdir/libnymea/types/interfaceparamtype.h \
$$top_srcdir/libnymea/types/interfacestatetype.h \
$$top_srcdir/libnymea/types/interfaceeventtype.h \
$$top_srcdir/libnymea/types/interfaceactiontype.h \