From 36a5aece0db3d34a771cfa80f13f30933f8eff5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Mon, 7 Jun 2021 15:28:53 +0200 Subject: [PATCH 1/3] Update discovery for Keba to nymea internal discovery mechanism --- keba/discovery.cpp | 271 --------------------------------- keba/discovery.h | 75 --------- keba/host.cpp | 94 ------------ keba/host.h | 70 --------- keba/integrationpluginkeba.cpp | 133 +++++++++------- keba/integrationpluginkeba.h | 6 +- keba/keba.pro | 4 - keba/kecontactdatalayer.cpp | 30 ++++ keba/kecontactdatalayer.h | 30 ++++ 9 files changed, 137 insertions(+), 576 deletions(-) delete mode 100644 keba/discovery.cpp delete mode 100644 keba/discovery.h delete mode 100644 keba/host.cpp delete mode 100644 keba/host.h diff --git a/keba/discovery.cpp b/keba/discovery.cpp deleted file mode 100644 index a41192c4..00000000 --- a/keba/discovery.cpp +++ /dev/null @@ -1,271 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* -* Copyright 2013 - 2020, nymea GmbH -* Contact: contact@nymea.io -* -* This file is part of nymea. -* This project including source code and documentation is protected by -* copyright law, and remains the property of nymea GmbH. All rights, including -* reproduction, publication, editing and translation, are reserved. The use of -* this project is subject to the terms of a license agreement to be concluded -* with nymea GmbH in accordance with the terms of use of nymea GmbH, available -* under https://nymea.io/license -* -* GNU Lesser General Public License Usage -* Alternatively, this project may be redistributed and/or modified under the -* terms of the GNU Lesser General Public License as published by the Free -* Software Foundation; version 3. This project is distributed in the hope that -* it will be useful, but WITHOUT ANY WARRANTY; without even the implied -* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public License -* along with this project. If not, see . -* -* For any further details and any questions please contact us under -* contact@nymea.io or see our FAQ/Licensing Information on -* https://nymea.io/license/faq -* -* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -#include "discovery.h" -#include "extern-plugininfo.h" - -#include -#include -#include -#include -#include - -Discovery::Discovery(QObject *parent) : QObject(parent) -{ - connect(&m_timeoutTimer, &QTimer::timeout, this, &Discovery::onTimeout); -} - -void Discovery::discoverHosts(int timeout) -{ - if (isRunning()) { - qCWarning(dcKebaKeContact()) << "Discovery already running. Cannot start twice."; - return; - } - m_timeoutTimer.start(timeout * 1000); - - foreach (const QString &target, getDefaultTargets()) { - QProcess *discoveryProcess = new QProcess(this); - m_discoveryProcesses.append(discoveryProcess); - connect(discoveryProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(discoveryFinished(int,QProcess::ExitStatus))); - - QStringList arguments; - arguments << "-oX" << "-" << "-n" << "-sn"; - arguments << target; - - qCDebug(dcKebaKeContact()) << "Scanning network:" << "nmap" << arguments.join(" "); - discoveryProcess->start(QStringLiteral("nmap"), arguments); - } -} - -void Discovery::abort() -{ - foreach (QProcess *discoveryProcess, m_discoveryProcesses) { - if (discoveryProcess->state() == QProcess::Running) { - qCDebug(dcKebaKeContact()) << "Kill running discovery process"; - discoveryProcess->terminate(); - discoveryProcess->waitForFinished(5000); - } - } - foreach (QProcess *p, m_pendingArpLookups.keys()) { - p->terminate(); - delete p; - } - m_pendingArpLookups.clear(); - m_pendingNameLookups.clear(); - qDeleteAll(m_scanResults); - m_scanResults.clear(); -} - -bool Discovery::isRunning() const -{ - return !m_discoveryProcesses.isEmpty() || !m_pendingArpLookups.isEmpty() || !m_pendingNameLookups.isEmpty(); -} - -void Discovery::discoveryFinished(int exitCode, QProcess::ExitStatus exitStatus) -{ - QProcess *discoveryProcess = static_cast(sender()); - - if (exitCode != 0 || exitStatus != QProcess::NormalExit) { - qCWarning(dcKebaKeContact()) << "Nmap error failed. Is nmap installed correctly?"; - m_discoveryProcesses.removeAll(discoveryProcess); - discoveryProcess->deleteLater(); - discoveryProcess = nullptr; - finishDiscovery(); - return; - } - - QByteArray data = discoveryProcess->readAll(); - m_discoveryProcesses.removeAll(discoveryProcess); - discoveryProcess->deleteLater(); - discoveryProcess = nullptr; - - QXmlStreamReader reader(data); - - int foundHosts = 0; - qCDebug(dcKebaKeContact()) << "nmap finished network discovery:"; - while (!reader.atEnd() && !reader.hasError()) { - QXmlStreamReader::TokenType token = reader.readNext(); - if(token == QXmlStreamReader::StartDocument) - continue; - - if(token == QXmlStreamReader::StartElement && reader.name() == "host") { - bool isUp = false; - QString address; - QString macAddress; - QString vendor; - while (!reader.atEnd() && !reader.hasError() && !(token == QXmlStreamReader::EndElement && reader.name() == "host")) { - token = reader.readNext(); - - if (reader.name() == "address") { - QString addr = reader.attributes().value("addr").toString(); - QString type = reader.attributes().value("addrtype").toString(); - if (type == "ipv4" && !addr.isEmpty()) { - address = addr; - } else if (type == "mac") { - macAddress = addr; - vendor = reader.attributes().value("vendor").toString(); - } - } - - if (reader.name() == "status") { - QString state = reader.attributes().value("state").toString(); - if (!state.isEmpty()) - isUp = state == "up"; - } - } - - if (isUp) { - foundHosts++; - qCDebug(dcKebaKeContact()) << " - host:" << address; - - Host *host = new Host(); - host->setAddress(address); - - if (!macAddress.isEmpty()) { - host->setMacAddress(macAddress); - } else { - QProcess *arpLookup = new QProcess(this); - m_pendingArpLookups.insert(arpLookup, host); - connect(arpLookup, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(arpLookupDone(int,QProcess::ExitStatus))); - arpLookup->start("arp", {"-vn"}); - } - - host->setHostName(vendor); - QHostInfo::lookupHost(address, this, SLOT(hostLookupDone(QHostInfo))); - m_pendingNameLookups.insert(address, host); - - m_scanResults.append(host); - } - } - } - - if (foundHosts == 0 && m_discoveryProcesses.isEmpty()) { - qCDebug(dcKebaKeContact()) << "Network scan successful but no hosts found in this network"; - finishDiscovery(); - } -} - -void Discovery::hostLookupDone(const QHostInfo &info) -{ - Host *host = m_pendingNameLookups.take(info.addresses().first().toString()); - if (!host) { - // Probably aborted... - return; - } - if (info.error() != QHostInfo::NoError) { - qWarning(dcKebaKeContact()) << "Host lookup failed:" << info.errorString(); - } - if (host->hostName().isEmpty() || info.hostName() != host->address()) { - host->setHostName(info.hostName()); - } - - finishDiscovery(); -} - -void Discovery::arpLookupDone(int exitCode, QProcess::ExitStatus exitStatus) -{ - QProcess *p = static_cast(sender()); - p->deleteLater(); - - Host *host = m_pendingArpLookups.take(p); - - if (exitCode != 0 || exitStatus != QProcess::NormalExit) { - qCWarning(dcKebaKeContact()) << "ARP lookup process failed for host" << host->address(); - finishDiscovery(); - return; - } - - QString data = QString::fromLatin1(p->readAll()); - foreach (QString line, data.split('\n')) { - line.replace(QRegExp("[ ]{1,}"), " "); - QStringList parts = line.split(" "); - if (parts.count() >= 3 && parts.first() == host->address() && parts.at(1) == "ether") { - host->setMacAddress(parts.at(2)); - break; - } - } - finishDiscovery(); -} - -void Discovery::onTimeout() -{ - qWarning(dcKebaKeContact()) << "Timeout hit. Stopping discovery"; - while (!m_discoveryProcesses.isEmpty()) { - QProcess *discoveryProcess = m_discoveryProcesses.takeFirst(); - disconnect(this, SLOT(discoveryFinished(int,QProcess::ExitStatus))); - discoveryProcess->terminate(); - delete discoveryProcess; - } - foreach (QProcess *p, m_pendingArpLookups.keys()) { - p->terminate(); - m_scanResults.removeAll(m_pendingArpLookups.value(p)); - delete p; - } - m_pendingArpLookups.clear(); - m_pendingNameLookups.clear(); - finishDiscovery(); -} - -QStringList Discovery::getDefaultTargets() -{ - QStringList targets; - foreach (const QHostAddress &interface, QNetworkInterface::allAddresses()) { - if (!interface.isLoopback() && interface.scopeId().isEmpty() && interface.protocol() == QAbstractSocket::IPv4Protocol) { - QPair pair = QHostAddress::parseSubnet(interface.toString() + "/24"); - QString newTarget = QString("%1/%2").arg(pair.first.toString()).arg(pair.second); - if (!targets.contains(newTarget)) { - targets.append(newTarget); - } - } - } - return targets; -} - -void Discovery::finishDiscovery() -{ - if (m_discoveryProcesses.count() > 0 || m_pendingNameLookups.count() > 0 || m_pendingArpLookups.count() > 0) { - // Still busy... - return; - } - - QList hosts; - foreach (Host *host, m_scanResults) { - if (!host->macAddress().isEmpty()) { - hosts.append(*host); - } - } - qDeleteAll(m_scanResults); - m_scanResults.clear(); - - qCDebug(dcKebaKeContact()) << "Found" << hosts.count() << "network devices"; - m_timeoutTimer.stop(); - emit finished(hosts); -} - diff --git a/keba/discovery.h b/keba/discovery.h deleted file mode 100644 index 02284c38..00000000 --- a/keba/discovery.h +++ /dev/null @@ -1,75 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* -* Copyright 2013 - 2020, nymea GmbH -* Contact: contact@nymea.io -* -* This file is part of nymea. -* This project including source code and documentation is protected by -* copyright law, and remains the property of nymea GmbH. All rights, including -* reproduction, publication, editing and translation, are reserved. The use of -* this project is subject to the terms of a license agreement to be concluded -* with nymea GmbH in accordance with the terms of use of nymea GmbH, available -* under https://nymea.io/license -* -* GNU Lesser General Public License Usage -* Alternatively, this project may be redistributed and/or modified under the -* terms of the GNU Lesser General Public License as published by the Free -* Software Foundation; version 3. This project is distributed in the hope that -* it will be useful, but WITHOUT ANY WARRANTY; without even the implied -* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public License -* along with this project. If not, see . -* -* For any further details and any questions please contact us under -* contact@nymea.io or see our FAQ/Licensing Information on -* https://nymea.io/license/faq -* -* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -#ifndef DISCOVERY_H -#define DISCOVERY_H - -#include -#include -#include -#include - -#include "host.h" - -class Discovery : public QObject -{ - Q_OBJECT -public: - explicit Discovery(QObject *parent = nullptr); - - void discoverHosts(int timeout); - void abort(); - bool isRunning() const; - -signals: - void finished(const QList &hosts); - -private: - QStringList getDefaultTargets(); - - void finishDiscovery(); - -private slots: - void discoveryFinished(int exitCode, QProcess::ExitStatus exitStatus); - void hostLookupDone(const QHostInfo &info); - void arpLookupDone(int exitCode, QProcess::ExitStatus exitStatus); - void onTimeout(); - -private: - QList m_discoveryProcesses; - QTimer m_timeoutTimer; - - QHash m_pendingArpLookups; - QHash m_pendingNameLookups; - QList m_scanResults; -}; - -#endif // DISCOVERY_H - diff --git a/keba/host.cpp b/keba/host.cpp deleted file mode 100644 index f1e80a53..00000000 --- a/keba/host.cpp +++ /dev/null @@ -1,94 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* -* Copyright 2013 - 2020, nymea GmbH -* Contact: contact@nymea.io -* -* This file is part of nymea. -* This project including source code and documentation is protected by -* copyright law, and remains the property of nymea GmbH. All rights, including -* reproduction, publication, editing and translation, are reserved. The use of -* this project is subject to the terms of a license agreement to be concluded -* with nymea GmbH in accordance with the terms of use of nymea GmbH, available -* under https://nymea.io/license -* -* GNU Lesser General Public License Usage -* Alternatively, this project may be redistributed and/or modified under the -* terms of the GNU Lesser General Public License as published by the Free -* Software Foundation; version 3. This project is distributed in the hope that -* it will be useful, but WITHOUT ANY WARRANTY; without even the implied -* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public License -* along with this project. If not, see . -* -* For any further details and any questions please contact us under -* contact@nymea.io or see our FAQ/Licensing Information on -* https://nymea.io/license/faq -* -* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -#include "host.h" - -Host::Host() -{ - qRegisterMetaType(); - qRegisterMetaType >(); -} - -QString Host::macAddress() const -{ - return m_macAddress; -} - -void Host::setMacAddress(const QString &macAddress) -{ - m_macAddress = macAddress; -} - -QString Host::hostName() const -{ - return m_hostName; -} - -void Host::setHostName(const QString &hostName) -{ - m_hostName = hostName; -} - -QString Host::address() const -{ - return m_address; -} - -void Host::setAddress(const QString &address) -{ - m_address = address; -} - -void Host::seen() -{ - m_lastSeenTime = QDateTime::currentDateTime(); -} - -QDateTime Host::lastSeenTime() const -{ - return m_lastSeenTime; -} - -bool Host::reachable() const -{ - return m_reachable; -} - -void Host::setReachable(bool reachable) -{ - m_reachable = reachable; -} - -QDebug operator<<(QDebug dbg, const Host &host) -{ - dbg.nospace() << "Host(" << host.macAddress() << "," << host.hostName() << ", " << host.address() << ", " << (host.reachable() ? "up" : "down") << ")"; - return dbg.space(); -} - diff --git a/keba/host.h b/keba/host.h deleted file mode 100644 index 8fe28a23..00000000 --- a/keba/host.h +++ /dev/null @@ -1,70 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* -* Copyright 2013 - 2020, nymea GmbH -* Contact: contact@nymea.io -* -* This file is part of nymea. -* This project including source code and documentation is protected by -* copyright law, and remains the property of nymea GmbH. All rights, including -* reproduction, publication, editing and translation, are reserved. The use of -* this project is subject to the terms of a license agreement to be concluded -* with nymea GmbH in accordance with the terms of use of nymea GmbH, available -* under https://nymea.io/license -* -* GNU Lesser General Public License Usage -* Alternatively, this project may be redistributed and/or modified under the -* terms of the GNU Lesser General Public License as published by the Free -* Software Foundation; version 3. This project is distributed in the hope that -* it will be useful, but WITHOUT ANY WARRANTY; without even the implied -* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public License -* along with this project. If not, see . -* -* For any further details and any questions please contact us under -* contact@nymea.io or see our FAQ/Licensing Information on -* https://nymea.io/license/faq -* -* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -#ifndef HOST_H -#define HOST_H - -#include -#include -#include - -class Host -{ -public: - Host(); - - QString macAddress() const; - void setMacAddress(const QString &macAddress); - - QString hostName() const; - void setHostName(const QString &hostName); - - QString address() const; - void setAddress(const QString &address); - - void seen(); - QDateTime lastSeenTime() const; - - bool reachable() const; - void setReachable(bool reachable); - -private: - QString m_macAddress; - QString m_hostName; - QString m_address; - QDateTime m_lastSeenTime; - bool m_reachable; -}; -Q_DECLARE_METATYPE(Host) - -QDebug operator<<(QDebug dbg, const Host &host); - -#endif // HOST_H - diff --git a/keba/integrationpluginkeba.cpp b/keba/integrationpluginkeba.cpp index 09efb12c..418cc6bb 100644 --- a/keba/integrationpluginkeba.cpp +++ b/keba/integrationpluginkeba.cpp @@ -28,11 +28,10 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -#include "integrationpluginkeba.h" #include "plugininfo.h" +#include "integrationpluginkeba.h" +#include "network/networkdevicediscovery.h" -#include -#include #include #include #include @@ -44,64 +43,52 @@ IntegrationPluginKeba::IntegrationPluginKeba() void IntegrationPluginKeba::init() { - m_discovery = new Discovery(this); - connect(m_discovery, &Discovery::finished, this, [this](const QList &hosts) { - foreach (const Host &host, hosts) { - if (!host.hostName().contains("keba", Qt::CaseSensitivity::CaseInsensitive)) - continue; - - foreach (Thing *existingThing, myThings()) { - if (existingThing->paramValue(wallboxThingMacAddressParamTypeId).toString().isEmpty()) { - //This device got probably manually setup, to enable auto rediscovery the MAC address needs to setup - if (existingThing->paramValue(wallboxThingIpAddressParamTypeId).toString() == host.address()) { - qCDebug(dcKebaKeContact()) << "Keba Wallbox MAC Address has been discovered" << existingThing->name() << host.macAddress(); - existingThing->setParamValue(wallboxThingMacAddressParamTypeId, host.macAddress()); - - } - } else if (existingThing->paramValue(wallboxThingMacAddressParamTypeId).toString() == host.macAddress()) { - if (existingThing->paramValue(wallboxThingIpAddressParamTypeId).toString() != host.address()) { - qCDebug(dcKebaKeContact()) << "Keba Wallbox IP Address has changed, from" << existingThing->paramValue(wallboxThingIpAddressParamTypeId).toString() << "to" << host.address(); - existingThing->setParamValue(wallboxThingIpAddressParamTypeId, host.address()); - - } else { - qCDebug(dcKebaKeContact()) << "Keba Wallbox" << existingThing->name() << "IP address has not changed" << host.address(); - } - break; - } - } - } - }); } void IntegrationPluginKeba::discoverThings(ThingDiscoveryInfo *info) { if (info->thingClassId() == wallboxThingClassId) { qCDebug(dcKebaKeContact()) << "Discovering Keba Wallbox"; - m_discovery->discoverHosts(25); - connect(m_discovery, &Discovery::finished, info, [this, info] (const QList &hosts) { - - foreach (const Host &host, hosts) { - if (!host.hostName().contains("keba", Qt::CaseSensitivity::CaseInsensitive)) + NetworkDeviceDiscoveryReply *discoveryReply = hardwareManager()->networkDeviceDiscovery()->discover(); + connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){ + ThingDescriptors descriptors; + qCDebug(dcKebaKeContact()) << "Discovery finished. Found" << discoveryReply->networkDevices().count() << "devices"; + foreach (const NetworkDevice &networkDevice, discoveryReply->networkDevices()) { + if (!networkDevice.hostName().contains("keba", Qt::CaseSensitivity::CaseInsensitive)) continue; - qCDebug(dcKebaKeContact()) << " - Keba Wallbox" << host.address() << host.macAddress(); - ThingDescriptor descriptor(wallboxThingClassId, "Wallbox", host.address() + " (" + host.macAddress() + ")"); - - // Rediscovery - foreach (Thing *existingThing, myThings()) { - if (existingThing->paramValue(wallboxThingMacAddressParamTypeId).toString() == host.macAddress()) { - qCDebug(dcKebaKeContact()) << " - Device is already added"; - descriptor.setThingId(existingThing->id()); - break; - } + qCDebug(dcKebaKeContact()) << " - Keba Wallbox" << networkDevice; + QString title = "Wallbox "; + if (networkDevice.hostName().isEmpty()) { + title += networkDevice.address().toString(); + } else { + title += networkDevice.hostName() + " (" + networkDevice.address().toString() + ")"; } + + QString description; + if (networkDevice.macAddressManufacturer().isEmpty()) { + description = networkDevice.macAddress(); + } else { + description = networkDevice.macAddress() + " (" + networkDevice.macAddressManufacturer() + ")"; + } + + ThingDescriptor descriptor(wallboxThingClassId, title, description); + + // Check if we already have set up this device + Things existingThings = myThings().filterByParam(wallboxThingMacAddressParamTypeId, networkDevice.macAddress()); + if (existingThings.count() == 1) { + qCDebug(dcKebaKeContact()) << "This wallbox already exists in the system!" << networkDevice; + descriptor.setThingId(existingThings.first()->id()); + } + ParamList params; - params << Param(wallboxThingMacAddressParamTypeId, host.macAddress()); - params << Param(wallboxThingIpAddressParamTypeId, host.address()); + params << Param(wallboxThingMacAddressParamTypeId, networkDevice.macAddress()); + params << Param(wallboxThingIpAddressParamTypeId, networkDevice.address().toString()); descriptor.setParams(params); info->addThingDescriptor(descriptor); } + info->finish(Thing::ThingErrorNoError); }); } else { @@ -179,12 +166,14 @@ void IntegrationPluginKeba::postSetupThing(Thing *thing) KeContact *keba = m_kebaDevices.value(thing->id()); if (!keba) { qCWarning(dcKebaKeContact()) << "No Keba connection found for this thing"; + return; } else { keba->getReport2(); keba->getReport3(); } + if (thing->paramValue(wallboxThingMacAddressParamTypeId).toString().isEmpty()) { - m_discovery->discoverHosts(25); + searchNetworkDevices(); } if (!m_updateTimer) { @@ -195,6 +184,7 @@ void IntegrationPluginKeba::postSetupThing(Thing *thing) KeContact *keba = m_kebaDevices.value(thing->id()); if (!keba) { qCWarning(dcKebaKeContact()) << "No Keba connection found for" << thing->name(); + return; } keba->getReport2(); keba->getReport3(); @@ -208,18 +198,13 @@ void IntegrationPluginKeba::postSetupThing(Thing *thing) if (!m_reconnectTimer) { m_reconnectTimer = hardwareManager()->pluginTimerManager()->registerTimer(60*5); connect(m_reconnectTimer, &PluginTimer::timeout, this, [this] { - Q_FOREACH(Thing *thing, myThings().filterByThingClassId(wallboxThingClassId)) { - if (thing->stateValue(wallboxConnectedStateTypeId) == false) { - m_discovery->discoverHosts(25); - break; // start discovery once for every device as soon as one device is disconnected - } - } + searchNetworkDevices(); }); } } void IntegrationPluginKeba::thingRemoved(Thing *thing) -{ +{ qCDebug(dcKebaKeContact()) << "Deleting" << thing->name(); if (thing->thingClassId() == wallboxThingClassId) { KeContact *keba = m_kebaDevices.take(thing->id()); @@ -284,6 +269,38 @@ void IntegrationPluginKeba::setDevicePlugState(Thing *thing, KeContact::PlugStat } } +void IntegrationPluginKeba::searchNetworkDevices() +{ + qCDebug(dcKebaKeContact()) << "Start searching for things..."; + NetworkDeviceDiscoveryReply *discoveryReply = hardwareManager()->networkDeviceDiscovery()->discover(); + connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){ + ThingDescriptors descriptors; + qCDebug(dcKebaKeContact()) << "Discovery finished. Found" << discoveryReply->networkDevices().count() << "devices"; + foreach (const NetworkDevice &networkDevice, discoveryReply->networkDevices()) { + if (!networkDevice.hostName().contains("keba", Qt::CaseSensitivity::CaseInsensitive)) + continue; + + foreach (Thing *existingThing, myThings().filterByThingClassId(wallboxThingClassId)) { + if (existingThing->paramValue(wallboxThingMacAddressParamTypeId).toString().isEmpty()) { + //This device got probably manually setup, to enable auto rediscovery the MAC address needs to setup + if (existingThing->paramValue(wallboxThingIpAddressParamTypeId).toString() == networkDevice.address().toString()) { + qCDebug(dcKebaKeContact()) << "Keba Wallbox MAC Address has been discovered" << existingThing->name() << networkDevice.macAddress(); + existingThing->setParamValue(wallboxThingMacAddressParamTypeId, networkDevice.macAddress()); + } + } else if (existingThing->paramValue(wallboxThingMacAddressParamTypeId).toString() == networkDevice.macAddress()) { + if (existingThing->paramValue(wallboxThingIpAddressParamTypeId).toString() != networkDevice.address().toString()) { + qCDebug(dcKebaKeContact()) << "Keba Wallbox IP Address has changed, from" << existingThing->paramValue(wallboxThingIpAddressParamTypeId).toString() << "to" << networkDevice.address().toString(); + existingThing->setParamValue(wallboxThingIpAddressParamTypeId, networkDevice.address().toString()); + } else { + qCDebug(dcKebaKeContact()) << "Keba Wallbox" << existingThing->name() << "IP address has not changed" << networkDevice.address().toString(); + } + break; + } + } + } + }); +} + void IntegrationPluginKeba::onConnectionChanged(bool status) { KeContact *keba = static_cast(sender()); @@ -294,7 +311,7 @@ void IntegrationPluginKeba::onConnectionChanged(bool status) } thing->setStateValue(wallboxConnectedStateTypeId, status); if (!status) { - m_discovery->discoverHosts(25); + searchNetworkDevices(); } } @@ -428,7 +445,7 @@ void IntegrationPluginKeba::onReport1XXReceived(int reportNumber, const KeContac // Report 100 is the current charging session if (report.endTime == 0) { // if the charing session is finished the end time will be set - double duration = (report.seconds - report.startTime)/60.00; + double duration = (report.seconds - report.startTime) / 60.00; thing->setStateValue(wallboxSessionTimeStateTypeId, duration); } else { // Charging session is finished and copied to Report 101 @@ -451,7 +468,7 @@ void IntegrationPluginKeba::onReport1XXReceived(int reportNumber, const KeContac params << Param(wallboxChargingSessionFinishedEventDurationParamTypeId, report.endTime); params << Param(wallboxChargingSessionFinishedEventIdParamTypeId); event.setParams(params); - emitEvent(event); + emit emitEvent(event); } } } else { diff --git a/keba/integrationpluginkeba.h b/keba/integrationpluginkeba.h index 438efce5..5543270e 100644 --- a/keba/integrationpluginkeba.h +++ b/keba/integrationpluginkeba.h @@ -35,11 +35,8 @@ #include "plugintimer.h" #include "kecontact.h" #include "kecontactdatalayer.h" -#include "discovery.h" -#include "host.h" #include -#include #include #include #include @@ -69,7 +66,6 @@ private: KeContactDataLayer *m_kebaData = nullptr; - Discovery *m_discovery = nullptr; QHash m_kebaDevices; QHash m_lastSessionId; @@ -78,6 +74,8 @@ private: void setDeviceState(Thing *device, KeContact::State state); void setDevicePlugState(Thing *device, KeContact::PlugState plugState); + void searchNetworkDevices(); + private slots: void onConnectionChanged(bool status); void onCommandExecuted(QUuid requestId, bool success); diff --git a/keba/keba.pro b/keba/keba.pro index 4507354c..ea4239f2 100644 --- a/keba/keba.pro +++ b/keba/keba.pro @@ -7,13 +7,9 @@ TARGET = $$qtLibraryTarget(nymea_integrationpluginkeba) SOURCES += \ integrationpluginkeba.cpp \ kecontact.cpp \ - discovery.cpp \ - host.cpp \ kecontactdatalayer.cpp HEADERS += \ integrationpluginkeba.h \ kecontact.h \ - discovery.h \ - host.h \ kecontactdatalayer.h diff --git a/keba/kecontactdatalayer.cpp b/keba/kecontactdatalayer.cpp index 2633631c..e1f3a9ec 100644 --- a/keba/kecontactdatalayer.cpp +++ b/keba/kecontactdatalayer.cpp @@ -1,3 +1,33 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + #include "kecontactdatalayer.h" #include "extern-plugininfo.h" diff --git a/keba/kecontactdatalayer.h b/keba/kecontactdatalayer.h index ae343ac8..b938bcd1 100644 --- a/keba/kecontactdatalayer.h +++ b/keba/kecontactdatalayer.h @@ -1,3 +1,33 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + #ifndef KECONTACTDATALAYER_H #define KECONTACTDATALAYER_H From a61e6fd2441af6271b746f4ed60e11024ffd0d6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Mon, 7 Jun 2021 17:15:39 +0200 Subject: [PATCH 2/3] Fix handling of IP address changes --- keba/integrationpluginkeba.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/keba/integrationpluginkeba.cpp b/keba/integrationpluginkeba.cpp index 418cc6bb..84ebb1dc 100644 --- a/keba/integrationpluginkeba.cpp +++ b/keba/integrationpluginkeba.cpp @@ -291,6 +291,12 @@ void IntegrationPluginKeba::searchNetworkDevices() if (existingThing->paramValue(wallboxThingIpAddressParamTypeId).toString() != networkDevice.address().toString()) { qCDebug(dcKebaKeContact()) << "Keba Wallbox IP Address has changed, from" << existingThing->paramValue(wallboxThingIpAddressParamTypeId).toString() << "to" << networkDevice.address().toString(); existingThing->setParamValue(wallboxThingIpAddressParamTypeId, networkDevice.address().toString()); + KeContact *keba = m_kebaDevices.value(existingThing->id()); + if (keba) { + keba->setAddress(QHostAddress(networkDevice.address())); + } else { + qCWarning(dcKebaKeContact()) << "Could not update IP address, for" << existingThing; + } } else { qCDebug(dcKebaKeContact()) << "Keba Wallbox" << existingThing->name() << "IP address has not changed" << networkDevice.address().toString(); } From 3218e279713fdd60d65ca3e83066222064ffbe65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Fri, 11 Jun 2021 11:06:29 +0200 Subject: [PATCH 3/3] Update discovery to renamed network device --- keba/integrationpluginkeba.cpp | 52 +++++++++++++++++----------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/keba/integrationpluginkeba.cpp b/keba/integrationpluginkeba.cpp index 84ebb1dc..371e32b9 100644 --- a/keba/integrationpluginkeba.cpp +++ b/keba/integrationpluginkeba.cpp @@ -53,38 +53,38 @@ void IntegrationPluginKeba::discoverThings(ThingDiscoveryInfo *info) NetworkDeviceDiscoveryReply *discoveryReply = hardwareManager()->networkDeviceDiscovery()->discover(); connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){ ThingDescriptors descriptors; - qCDebug(dcKebaKeContact()) << "Discovery finished. Found" << discoveryReply->networkDevices().count() << "devices"; - foreach (const NetworkDevice &networkDevice, discoveryReply->networkDevices()) { - if (!networkDevice.hostName().contains("keba", Qt::CaseSensitivity::CaseInsensitive)) + qCDebug(dcKebaKeContact()) << "Discovery finished. Found" << discoveryReply->networkDeviceInfos().count() << "devices"; + foreach (const NetworkDeviceInfo &networkDeviceInfo, discoveryReply->networkDeviceInfos()) { + if (!networkDeviceInfo.hostName().contains("keba", Qt::CaseSensitivity::CaseInsensitive)) continue; - qCDebug(dcKebaKeContact()) << " - Keba Wallbox" << networkDevice; + qCDebug(dcKebaKeContact()) << " - Keba Wallbox" << networkDeviceInfo; QString title = "Wallbox "; - if (networkDevice.hostName().isEmpty()) { - title += networkDevice.address().toString(); + if (networkDeviceInfo.hostName().isEmpty()) { + title += networkDeviceInfo.address().toString(); } else { - title += networkDevice.hostName() + " (" + networkDevice.address().toString() + ")"; + title += networkDeviceInfo.hostName() + " (" + networkDeviceInfo.address().toString() + ")"; } QString description; - if (networkDevice.macAddressManufacturer().isEmpty()) { - description = networkDevice.macAddress(); + if (networkDeviceInfo.macAddressManufacturer().isEmpty()) { + description = networkDeviceInfo.macAddress(); } else { - description = networkDevice.macAddress() + " (" + networkDevice.macAddressManufacturer() + ")"; + description = networkDeviceInfo.macAddress() + " (" + networkDeviceInfo.macAddressManufacturer() + ")"; } ThingDescriptor descriptor(wallboxThingClassId, title, description); // Check if we already have set up this device - Things existingThings = myThings().filterByParam(wallboxThingMacAddressParamTypeId, networkDevice.macAddress()); + Things existingThings = myThings().filterByParam(wallboxThingMacAddressParamTypeId, networkDeviceInfo.macAddress()); if (existingThings.count() == 1) { - qCDebug(dcKebaKeContact()) << "This wallbox already exists in the system!" << networkDevice; + qCDebug(dcKebaKeContact()) << "This wallbox already exists in the system!" << networkDeviceInfo; descriptor.setThingId(existingThings.first()->id()); } ParamList params; - params << Param(wallboxThingMacAddressParamTypeId, networkDevice.macAddress()); - params << Param(wallboxThingIpAddressParamTypeId, networkDevice.address().toString()); + params << Param(wallboxThingMacAddressParamTypeId, networkDeviceInfo.macAddress()); + params << Param(wallboxThingIpAddressParamTypeId, networkDeviceInfo.address().toString()); descriptor.setParams(params); info->addThingDescriptor(descriptor); } @@ -275,30 +275,30 @@ void IntegrationPluginKeba::searchNetworkDevices() NetworkDeviceDiscoveryReply *discoveryReply = hardwareManager()->networkDeviceDiscovery()->discover(); connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){ ThingDescriptors descriptors; - qCDebug(dcKebaKeContact()) << "Discovery finished. Found" << discoveryReply->networkDevices().count() << "devices"; - foreach (const NetworkDevice &networkDevice, discoveryReply->networkDevices()) { - if (!networkDevice.hostName().contains("keba", Qt::CaseSensitivity::CaseInsensitive)) + qCDebug(dcKebaKeContact()) << "Discovery finished. Found" << discoveryReply->networkDeviceInfos().count() << "devices"; + foreach (const NetworkDeviceInfo &networkDeviceInfo, discoveryReply->networkDeviceInfos()) { + if (!networkDeviceInfo.hostName().contains("keba", Qt::CaseSensitivity::CaseInsensitive)) continue; foreach (Thing *existingThing, myThings().filterByThingClassId(wallboxThingClassId)) { if (existingThing->paramValue(wallboxThingMacAddressParamTypeId).toString().isEmpty()) { //This device got probably manually setup, to enable auto rediscovery the MAC address needs to setup - if (existingThing->paramValue(wallboxThingIpAddressParamTypeId).toString() == networkDevice.address().toString()) { - qCDebug(dcKebaKeContact()) << "Keba Wallbox MAC Address has been discovered" << existingThing->name() << networkDevice.macAddress(); - existingThing->setParamValue(wallboxThingMacAddressParamTypeId, networkDevice.macAddress()); + if (existingThing->paramValue(wallboxThingIpAddressParamTypeId).toString() == networkDeviceInfo.address().toString()) { + qCDebug(dcKebaKeContact()) << "Keba Wallbox MAC Address has been discovered" << existingThing->name() << networkDeviceInfo.macAddress(); + existingThing->setParamValue(wallboxThingMacAddressParamTypeId, networkDeviceInfo.macAddress()); } - } else if (existingThing->paramValue(wallboxThingMacAddressParamTypeId).toString() == networkDevice.macAddress()) { - if (existingThing->paramValue(wallboxThingIpAddressParamTypeId).toString() != networkDevice.address().toString()) { - qCDebug(dcKebaKeContact()) << "Keba Wallbox IP Address has changed, from" << existingThing->paramValue(wallboxThingIpAddressParamTypeId).toString() << "to" << networkDevice.address().toString(); - existingThing->setParamValue(wallboxThingIpAddressParamTypeId, networkDevice.address().toString()); + } else if (existingThing->paramValue(wallboxThingMacAddressParamTypeId).toString() == networkDeviceInfo.macAddress()) { + if (existingThing->paramValue(wallboxThingIpAddressParamTypeId).toString() != networkDeviceInfo.address().toString()) { + qCDebug(dcKebaKeContact()) << "Keba Wallbox IP Address has changed, from" << existingThing->paramValue(wallboxThingIpAddressParamTypeId).toString() << "to" << networkDeviceInfo.address().toString(); + existingThing->setParamValue(wallboxThingIpAddressParamTypeId, networkDeviceInfo.address().toString()); KeContact *keba = m_kebaDevices.value(existingThing->id()); if (keba) { - keba->setAddress(QHostAddress(networkDevice.address())); + keba->setAddress(QHostAddress(networkDeviceInfo.address())); } else { qCWarning(dcKebaKeContact()) << "Could not update IP address, for" << existingThing; } } else { - qCDebug(dcKebaKeContact()) << "Keba Wallbox" << existingThing->name() << "IP address has not changed" << networkDevice.address().toString(); + qCDebug(dcKebaKeContact()) << "Keba Wallbox" << existingThing->name() << "IP address has not changed" << networkDeviceInfo.address().toString(); } break; }