From 047367180a8157ea1781bd7600915a4f906a0dd4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Simon=20St=C3=BCrz?= <simon.stuerz@nymea.io>
Date: Wed, 11 Dec 2024 15:57:44 +0100
Subject: [PATCH] Keba: Update to networkdevice interface

---
 keba/integrationpluginkeba.cpp  | 24 +++++++----
 keba/integrationpluginkeba.h    |  2 +
 keba/integrationpluginkeba.json | 42 +++++++++++++++++---
 keba/kebadiscovery.cpp          | 70 ++++++++++++++-------------------
 keba/kebadiscovery.h            | 14 +++----
 5 files changed, 90 insertions(+), 62 deletions(-)

diff --git a/keba/integrationpluginkeba.cpp b/keba/integrationpluginkeba.cpp
index 9e2c9183f..d96ef2edc 100644
--- a/keba/integrationpluginkeba.cpp
+++ b/keba/integrationpluginkeba.cpp
@@ -50,6 +50,12 @@ void IntegrationPluginKeba::init()
     m_macAddressParamTypeIds.insert(kebaThingClassId, kebaThingMacAddressParamTypeId);
     m_macAddressParamTypeIds.insert(kebaSimpleThingClassId, kebaSimpleThingMacAddressParamTypeId);
 
+    m_hostNameParamTypeIds.insert(kebaThingClassId, kebaThingHostNameParamTypeId);
+    m_hostNameParamTypeIds.insert(kebaSimpleThingClassId, kebaSimpleThingHostNameParamTypeId);
+
+    m_addressParamTypeIds.insert(kebaThingClassId, kebaThingAddressParamTypeId);
+    m_addressParamTypeIds.insert(kebaSimpleThingClassId, kebaSimpleThingAddressParamTypeId);
+
     m_modelParamTypeIds.insert(kebaThingClassId, kebaThingModelParamTypeId);
     m_modelParamTypeIds.insert(kebaSimpleThingClassId, kebaSimpleThingModelParamTypeId);
 
@@ -104,14 +110,16 @@ void IntegrationPluginKeba::discoverThings(ThingDiscoveryInfo *info)
             qCDebug(dcKeba()) << "Discovered:" << descriptor.title() << descriptor.description();
 
             // Check if we already have set up this device
-            Things existingThings = myThings().filterByParam(m_macAddressParamTypeIds.value(discoveredThingClassId), result.networkDeviceInfo.macAddress());
+            Things existingThings = myThings().filterByParam(m_serialNumberParamTypeIds.value(discoveredThingClassId), result.serialNumber);
             if (existingThings.count() == 1) {
                 qCDebug(dcKeba()) << "This keba already exists in the system!" << result.networkDeviceInfo;
                 descriptor.setThingId(existingThings.first()->id());
             }
 
             ParamList params;
-            params << Param(m_macAddressParamTypeIds.value(discoveredThingClassId), result.networkDeviceInfo.macAddress());
+            params << Param(m_macAddressParamTypeIds.value(discoveredThingClassId), result.networkDeviceInfo.thingParamValueMacAddress());
+            params << Param(m_hostNameParamTypeIds.value(discoveredThingClassId), result.networkDeviceInfo.thingParamValueHostName());
+            params << Param(m_addressParamTypeIds.value(discoveredThingClassId), result.networkDeviceInfo.thingParamValueAddress());
             params << Param(m_modelParamTypeIds.value(discoveredThingClassId), result.product);
             params << Param(m_serialNumberParamTypeIds.value(discoveredThingClassId), result.serialNumber);
             descriptor.setParams(params);
@@ -156,16 +164,16 @@ void IntegrationPluginKeba::setupThing(ThingSetupInfo *info)
         }
     }
 
+
+    // Create a monitor so we always get the correct IP in the network and see if the device is reachable without polling on our own
+    NetworkDeviceMonitor *monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(thing);
     // Make sure we have a valid mac address, otherwise no monitor and not auto searching is possible
-    MacAddress macAddress = MacAddress(thing->paramValue(m_macAddressParamTypeIds.value(thing->thingClassId())).toString());
-    if (macAddress.isNull()) {
-        qCWarning(dcKeba()) << "Failed to set up keba because the MAC address is not valid:" << thing->paramValue(m_macAddressParamTypeIds.value(thing->thingClassId())).toString() << macAddress.toString();
-        info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("The MAC address is not vaild. Please reconfigure the device to fix this."));
+    if (!monitor) {
+        qCWarning(dcKeba()) << "Can not set up connection monitor with the given parameters:" << thing->params();
+        info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("Unable to set up the connection with this configuration. Please reconfigure the connection."));
         return;
     }
 
-    // Create a monitor so we always get the correct IP in the network and see if the device is reachable without polling on our own
-    NetworkDeviceMonitor *monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(macAddress);
     connect(monitor, &NetworkDeviceMonitor::reachableChanged, thing, [=](bool reachable){
         // Only if the setup has been finished
         KeContact *keba = m_kebaDevices.value(thing->id());
diff --git a/keba/integrationpluginkeba.h b/keba/integrationpluginkeba.h
index 0e347a97a..a58cc2dbd 100644
--- a/keba/integrationpluginkeba.h
+++ b/keba/integrationpluginkeba.h
@@ -77,6 +77,8 @@ class IntegrationPluginKeba : public IntegrationPlugin
     KebaDiscovery *m_runningDiscovery = nullptr;
 
     QHash<ThingClassId, ParamTypeId> m_macAddressParamTypeIds;
+    QHash<ThingClassId, ParamTypeId> m_hostNameParamTypeIds;
+    QHash<ThingClassId, ParamTypeId> m_addressParamTypeIds;
     QHash<ThingClassId, ParamTypeId> m_modelParamTypeIds;
     QHash<ThingClassId, ParamTypeId> m_serialNumberParamTypeIds;
 
diff --git a/keba/integrationpluginkeba.json b/keba/integrationpluginkeba.json
index 44af0a971..260e61f8e 100644
--- a/keba/integrationpluginkeba.json
+++ b/keba/integrationpluginkeba.json
@@ -13,24 +13,40 @@
                     "name": "keba",
                     "displayName": "Keba KeContact",
                     "createMethods": ["discovery", "user"],
-                    "interfaces": ["evcharger", "smartmeterconsumer", "connectable"],
+                    "interfaces": ["evcharger", "smartmeterconsumer", "connectable", "networkdevice"],
                     "paramTypes":[
                         {
                             "id": "c2df921d-ff8b-411c-9b1d-04a437d7dfa6",
                             "name": "macAddress",
                             "displayName": "MAC address",
                             "type": "QString",
-                            "inputType": "TextLine",
-                            "defaultValue":"",
+                            "inputType": "MacAddress",
+                            "defaultValue": "",
                             "readOnly": true
                         },
+                        {
+                            "id": "0cc79bb7-3162-432c-a7bc-45f9b5542fbd",
+                            "name": "hostName",
+                            "displayName": "Host name",
+                            "type": "QString",
+                            "inputType": "TextLine",
+                            "defaultValue": ""
+                        },
+                        {
+                            "id": "22f70789-33c2-4183-8bca-0d3108b1c80b",
+                            "name": "address",
+                            "displayName": "IP address",
+                            "type": "QString",
+                            "inputType": "IPv4Address",
+                            "defaultValue": ""
+                        },
                         {
                             "id": "45255155-318b-4204-8ce6-2c106a56286d",
                             "name": "serialNumber",
                             "displayName": "Serial number",
                             "type": "QString",
                             "inputType": "TextLine",
-                            "defaultValue":"",
+                            "defaultValue": "",
                             "readOnly": true
                         },
                         {
@@ -39,7 +55,7 @@
                             "displayName": "Product name",
                             "type": "QString",
                             "inputType": "TextLine",
-                            "defaultValue":"",
+                            "defaultValue": "",
                             "readOnly": true
                         }
                     ],
@@ -410,6 +426,22 @@
                             "defaultValue":"",
                             "readOnly": true
                         },
+                        {
+                            "id": "96104d2b-2fd3-4f20-bc8c-7b873d0b0f9e",
+                            "name": "hostName",
+                            "displayName": "Host name",
+                            "type": "QString",
+                            "inputType": "TextLine",
+                            "defaultValue": ""
+                        },
+                        {
+                            "id": "a0c60511-4082-4109-9a9f-383076680c4f",
+                            "name": "address",
+                            "displayName": "IP address",
+                            "type": "QString",
+                            "inputType": "IPv4Address",
+                            "defaultValue": ""
+                        },
                         {
                             "id": "6f732eb9-1711-4da0-a9a4-abcfa19f5e34",
                             "name": "serialNumber",
diff --git a/keba/kebadiscovery.cpp b/keba/kebadiscovery.cpp
index 054b2a57f..6af9a5353 100644
--- a/keba/kebadiscovery.cpp
+++ b/keba/kebadiscovery.cpp
@@ -1,6 +1,6 @@
 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
-* Copyright 2013 - 2021, nymea GmbH
+* Copyright 2013 - 2024, nymea GmbH
 * Contact: contact@nymea.io
 *
 * This file is part of nymea.
@@ -44,6 +44,12 @@ KebaDiscovery::KebaDiscovery(KeContactDataLayer *kebaDataLayer, NetworkDeviceDis
     m_responseTimer.setInterval(2000);
     m_responseTimer.setSingleShot(true);
     connect(&m_responseTimer, &QTimer::timeout, this, [=](){
+
+        // Fill in all network device infos we have
+        for (int i = 0; i < m_results.count(); i++) {
+            m_results[i].networkDeviceInfo = m_networkDeviceInfos.get(m_results.at(i).address);
+        }
+
         qCInfo(dcKeba()) << "Discovery: Finished successfully. Found" << m_results.count() << "Keba Wallbox";
         emit discoveryFinished();
     });
@@ -51,12 +57,6 @@ KebaDiscovery::KebaDiscovery(KeContactDataLayer *kebaDataLayer, NetworkDeviceDis
     // Read data from the keba data layer and verify if it is a keba report
     connect (m_kebaDataLayer, &KeContactDataLayer::datagramReceived, this, [=](const QHostAddress &address, const QByteArray &datagram){
 
-        // Just continue if this is a new address we have no result for
-        if (alreadyDiscovered(address)) {
-            qCDebug(dcKeba()) << "Discovery: Skipping datagram from already discovered Keba on" << address.toString();
-            return;
-        }
-
         // Try to convert the received data to a json document
         QJsonParseError error;
         QJsonDocument jsonDoc = QJsonDocument::fromJson(datagram, &error);
@@ -78,15 +78,23 @@ KebaDiscovery::KebaDiscovery(KeContactDataLayer *kebaDataLayer, NetworkDeviceDis
         }
 
         // We have received a report 1 datagram, let's add it to the result
-        NetworkDeviceInfo networkDeviceInfo = m_verifiedNetworkDeviceInfos.get(address);
-        if (networkDeviceInfo.isValid()) {
-            KebaDiscoveryResult result;
-            result.networkDeviceInfo = networkDeviceInfo;
-            result.product = dataMap.value("Product").toString();
-            result.serialNumber = dataMap.value("Serial").toString();
-            result.firmwareVersion = dataMap.value("Firmware").toString();
+        KebaDiscoveryResult result;
+        result.address = address;
+        result.product = dataMap.value("Product").toString();
+        result.serialNumber = dataMap.value("Serial").toString();
+        result.firmwareVersion = dataMap.value("Firmware").toString();
+
+        bool alreadyDiscovered = false;
+        foreach (const KebaDiscoveryResult &r, m_results) {
+            if (r.serialNumber == result.serialNumber) {
+                alreadyDiscovered = true;
+                break;
+            }
+        }
+
+        if (!alreadyDiscovered) {
             m_results.append(result);
-            qCDebug(dcKeba()) << "Discovery: -->" << networkDeviceInfo << networkDeviceInfo.macAddress() << result.product << result.serialNumber << result.firmwareVersion;
+            qCDebug(dcKeba()) << "Discovery: -->" << address.toString() << result.product << result.serialNumber << result.firmwareVersion;
         }
     });
 }
@@ -104,26 +112,16 @@ void KebaDiscovery::startDiscovery()
     qCInfo(dcKeba()) << "Discovery: Start searching for Keba wallboxes in the network...";
     NetworkDeviceDiscoveryReply *discoveryReply = m_networkDeviceDiscovery->discover();
 
-    // Check any already discovered infos..
-    foreach (const NetworkDeviceInfo &networkDeviceInfo, discoveryReply->networkDeviceInfos()) {
-        sendReportRequest(networkDeviceInfo);
-    }
-
     // Imedialty check any new device gets discovered
-    connect(discoveryReply, &NetworkDeviceDiscoveryReply::networkDeviceInfoAdded, this, &KebaDiscovery::sendReportRequest);
+    connect(discoveryReply, &NetworkDeviceDiscoveryReply::hostAddressDiscovered, this, &KebaDiscovery::sendReportRequest);
 
     // Check what might be left on finished
     connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, discoveryReply, &NetworkDeviceDiscoveryReply::deleteLater);
     connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){
         qCDebug(dcKeba()) << "Discovery: Network discovery finished. Found" << discoveryReply->networkDeviceInfos().count() << "network devices";
         m_networkDeviceInfos = discoveryReply->networkDeviceInfos();
+
         qCDebug(dcKeba()) << "Discovery: Network discovery finished. Start finishing discovery...";
-        // Send a report request to nework device info not sent already...
-        foreach (const NetworkDeviceInfo &networkDeviceInfo, m_networkDeviceInfos) {
-            if (!m_verifiedNetworkDeviceInfos.contains(networkDeviceInfo)) {
-                sendReportRequest(networkDeviceInfo);
-            }
-        }
         m_responseTimer.start();
     });
 }
@@ -133,27 +131,17 @@ QList<KebaDiscovery::KebaDiscoveryResult> KebaDiscovery::discoveryResults() cons
     return m_results;
 }
 
-bool KebaDiscovery::alreadyDiscovered(const QHostAddress &address)
+void KebaDiscovery::sendReportRequest(const QHostAddress &address)
 {
-    foreach (const KebaDiscoveryResult &result, m_results) {
-        if (result.networkDeviceInfo.address() == address) {
-            return true;
-        }
-    }
-
-    return false;
+    m_verifiedAddresses.append(address);
+    m_kebaDataLayer->write(address, QByteArray("report 1\n"));
 }
 
 void KebaDiscovery::cleanup()
 {
     m_networkDeviceInfos.clear();
-    m_verifiedNetworkDeviceInfos.clear();
+    m_verifiedAddresses.clear();
     m_results.clear();
 }
 
-void KebaDiscovery::sendReportRequest(const NetworkDeviceInfo &networkDeviceInfo)
-{
-    m_verifiedNetworkDeviceInfos.append(networkDeviceInfo);
-    m_kebaDataLayer->write(networkDeviceInfo.address(), QByteArray("report 1\n"));
-}
 
diff --git a/keba/kebadiscovery.h b/keba/kebadiscovery.h
index df36c0dd1..eaead1ddc 100644
--- a/keba/kebadiscovery.h
+++ b/keba/kebadiscovery.h
@@ -1,6 +1,6 @@
 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
-* Copyright 2013 - 2021, nymea GmbH
+* Copyright 2013 - 2024, nymea GmbH
 * Contact: contact@nymea.io
 *
 * This file is part of nymea.
@@ -47,6 +47,7 @@ class KebaDiscovery : public QObject
         QString product;
         QString serialNumber;
         QString firmwareVersion;
+        QHostAddress address;
         NetworkDeviceInfo networkDeviceInfo;
     } KebaDiscoveryResult;
 
@@ -60,22 +61,19 @@ class KebaDiscovery : public QObject
 signals:
     void discoveryFinished();
 
+private slots:
+    void sendReportRequest(const QHostAddress &address);
+
 private:
     KeContactDataLayer *m_kebaDataLayer = nullptr;
     NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr;
     QTimer m_responseTimer;
 
     NetworkDeviceInfos m_networkDeviceInfos;
-    NetworkDeviceInfos m_verifiedNetworkDeviceInfos;
+    QList<QHostAddress> m_verifiedAddresses;
     QList<KebaDiscoveryResult> m_results;
 
-    bool alreadyDiscovered(const QHostAddress &address);
-
     void cleanup();
-
-private slots:
-    void sendReportRequest(const NetworkDeviceInfo &networkDeviceInfo);
-
 };
 
 #endif // KEBADISCOVERY_H