diff --git a/goecharger/goediscovery.cpp b/goecharger/goediscovery.cpp index e46e5b0a..b6129c53 100644 --- a/goecharger/goediscovery.cpp +++ b/goecharger/goediscovery.cpp @@ -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,10 +34,11 @@ #include #include -GoeDiscovery::GoeDiscovery(NetworkAccessManager *networkAccessManager, NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent) : - QObject(parent), - m_networkAccessManager(networkAccessManager), - m_networkDeviceDiscovery(networkDeviceDiscovery) +GoeDiscovery::GoeDiscovery(NetworkAccessManager *networkAccessManager, NetworkDeviceDiscovery *networkDeviceDiscovery, ZeroConfServiceBrowser *serviceBrowser, QObject *parent) : + QObject{parent}, + m_networkAccessManager{networkAccessManager}, + m_networkDeviceDiscovery{networkDeviceDiscovery}, + m_serviceBrowser{serviceBrowser} { } @@ -57,6 +58,14 @@ void GoeDiscovery::startDiscovery() m_startDateTime = QDateTime::currentDateTime(); qCInfo(dcGoECharger()) << "Discovery: Start discovering the network..."; + + // ZeroConf + connect(m_serviceBrowser, &ZeroConfServiceBrowser::serviceEntryAdded, this, &GoeDiscovery::onServiceEntryAdded); + foreach (const ZeroConfServiceEntry &serviceEntry, m_serviceBrowser->serviceEntries()) { + onServiceEntryAdded(serviceEntry); + } + + // Network discovery m_discoveryReply = m_networkDeviceDiscovery->discover(); // Test any network device beeing discovered @@ -112,6 +121,11 @@ QNetworkRequest GoeDiscovery::buildRequestV2(const QHostAddress &address) return QNetworkRequest(requestUrl); } +bool GoeDiscovery::isGoeCharger(const ZeroConfServiceEntry &serviceEntry) +{ + return serviceEntry.name().toLower().contains("go-echarger"); +} + void GoeDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo) { // Make sure we have not checked this host yet @@ -154,6 +168,12 @@ void GoeDiscovery::checkNetworkDeviceApiV1(const NetworkDeviceInfo &networkDevic // Looks like we have found a go-e V1 api endpoint, nice qCDebug(dcGoECharger()) << "Discovery: --> Found API V1 on" << networkDeviceInfo.address().toString(); + if (m_discoveryResults.contains(networkDeviceInfo.address()) && m_discoveryResults.value(networkDeviceInfo.address()).discoveryMethod == DiscoveryMethodZeroConf) { + qCDebug(dcGoECharger()) << "Discovery: Network discovery found API V1 go-eCharger on" << networkDeviceInfo.address().toString() + << "but this host has already been discovered using ZeroConf. Prefering ZeroConf over MAC address due to Repeater missbehaviours."; + return; + } + if (m_discoveryResults.contains(networkDeviceInfo.address())) { // We use the information from API V2 since there are more information available m_discoveryResults[networkDeviceInfo.address()].apiAvailableV1 = true; @@ -209,11 +229,19 @@ void GoeDiscovery::checkNetworkDeviceApiV2(const NetworkDeviceInfo &networkDevic result.product = responseMap.value("typ").toString(); result.friendlyName = responseMap.value("fna").toString(); result.networkDeviceInfo = networkDeviceInfo; + result.discoveryMethod = DiscoveryMethodNetwork; result.apiAvailableV2 = true; + if (m_discoveryResults.contains(networkDeviceInfo.address()) && m_discoveryResults.value(networkDeviceInfo.address()).discoveryMethod == DiscoveryMethodZeroConf) { + qCDebug(dcGoECharger()) << "Discovery: Network discovery found API V2 go-eCharger on" << networkDeviceInfo.address().toString() + << "but this host has already been discovered using ZeroConf. Prefering ZeroConf over MAC address due to Repeater missbehaviours."; + return; + } + if (m_discoveryResults.contains(networkDeviceInfo.address())) { result.apiAvailableV1 = m_discoveryResults.value(networkDeviceInfo.address()).apiAvailableV1; } + // Overwrite result from V1 since V2 contains more information m_discoveryResults[networkDeviceInfo.address()] = result; } else { @@ -222,6 +250,33 @@ void GoeDiscovery::checkNetworkDeviceApiV2(const NetworkDeviceInfo &networkDevic }); } +void GoeDiscovery::onServiceEntryAdded(const ZeroConfServiceEntry &serviceEntry) +{ + // Note: we always prefere the zeroconf discovery over the network discovery. Some networks use wifi repeaters, + // which spoof the mac address and multipe IP have the same mac address. Using zeroconf and have IP based discovery + // solves this issue + + if (isGoeCharger(serviceEntry)) { + qCDebug(dcGoECharger()) << "Discovery: Found ZeroConf go-eCharger" << serviceEntry; + + GoeDiscovery::Result result; + result.serialNumber = serviceEntry.txt("serial"); + result.firmwareVersion = serviceEntry.txt("version"); + result.manufacturer = serviceEntry.txt("manufacturer"); + result.product = serviceEntry.txt("devicetype"); + result.friendlyName = serviceEntry.txt("friendly_name"); + result.discoveryMethod = DiscoveryMethodZeroConf; + result.apiAvailableV1 = serviceEntry.txt("protocol").toUInt() == 1; + result.apiAvailableV2 = serviceEntry.txt("protocol").toUInt() == 2; + result.address = serviceEntry.hostAddress(); + + qCDebug(dcGoECharger()) << "Discovery:" << result; + + // Overwrite any already discovered result for this host, we always prefere ZeroConf over Networkdiscovery... + m_discoveryResults[result.address] = result; + } +} + void GoeDiscovery::cleanupPendingReplies() { foreach (QNetworkReply *reply, m_pendingReplies) { @@ -232,6 +287,9 @@ void GoeDiscovery::cleanupPendingReplies() void GoeDiscovery::finishDiscovery() { + disconnect(m_serviceBrowser, &ZeroConfServiceBrowser::serviceEntryAdded, this, &GoeDiscovery::onServiceEntryAdded); + + qint64 durationMilliSeconds = QDateTime::currentMSecsSinceEpoch() - m_startDateTime.toMSecsSinceEpoch(); qCInfo(dcGoECharger()) << "Discovery: Finished the discovery process. Found" << m_discoveryResults.count() << "go-eChargers in" << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz"); cleanupPendingReplies(); @@ -246,8 +304,13 @@ QDebug operator<<(QDebug dbg, const GoeDiscovery::Result &result) dbg.nospace() << ", SN: " << result.serialNumber; dbg.nospace() << ", V1: " << result.apiAvailableV1; dbg.nospace() << ", V2: " << result.apiAvailableV2; - dbg.nospace() << ", " << result.networkDeviceInfo.address().toString(); - dbg.nospace() << ", " << result.networkDeviceInfo.macAddress(); + if (result.discoveryMethod == GoeDiscovery::DiscoveryMethodZeroConf) { + dbg.nospace() << ", " << result.discoveryMethod; + dbg.nospace() << ", " << result.address.toString(); + } else { + dbg.nospace() << ", " << result.networkDeviceInfo.address().toString(); + dbg.nospace() << ", " << result.networkDeviceInfo.macAddress(); + } dbg.nospace() << ") "; return dbg.maybeSpace(); } diff --git a/goecharger/goediscovery.h b/goecharger/goediscovery.h index 1c05d8c4..8be2a10a 100644 --- a/goecharger/goediscovery.h +++ b/goecharger/goediscovery.h @@ -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,23 +36,34 @@ #include #include +#include +#include +#include class GoeDiscovery : public QObject { Q_OBJECT public: + enum DiscoveryMethod { + DiscoveryMethodNetwork, + DiscoveryMethodZeroConf, + }; + Q_ENUM(DiscoveryMethod) + typedef struct Result { QString product = "go-eCharger"; QString manufacturer = "go-e"; QString friendlyName; QString serialNumber; QString firmwareVersion; - NetworkDeviceInfo networkDeviceInfo; + DiscoveryMethod discoveryMethod; + NetworkDeviceInfo networkDeviceInfo; // Network discovery + QHostAddress address; // ZeroConf bool apiAvailableV1 = false; bool apiAvailableV2 = false; } Result; - explicit GoeDiscovery(NetworkAccessManager *networkAccessManager, NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent = nullptr); + explicit GoeDiscovery(NetworkAccessManager *networkAccessManager, NetworkDeviceDiscovery *networkDeviceDiscovery, ZeroConfServiceBrowser *serviceBrowser, QObject *parent = nullptr); ~GoeDiscovery(); void startDiscovery(); @@ -62,6 +73,9 @@ public: static QNetworkRequest buildRequestV1(const QHostAddress &address); static QNetworkRequest buildRequestV2(const QHostAddress &address); + // Zeroconf service helpers + static bool isGoeCharger(const ZeroConfServiceEntry &serviceEntry); + signals: void discoveryFinished(); @@ -70,10 +84,12 @@ private: NetworkAccessManager *m_networkAccessManager = nullptr; NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr; NetworkDeviceDiscoveryReply *m_discoveryReply = nullptr; + ZeroConfServiceBrowser *m_serviceBrowser = nullptr; QHash m_discoveryResults; NetworkDeviceInfos m_discoveredNetworkDeviceInfos; NetworkDeviceInfos m_verifiedNetworkDeviceInfos; + QList m_pendingReplies; private slots: @@ -81,6 +97,8 @@ private slots: void checkNetworkDeviceApiV1(const NetworkDeviceInfo &networkDeviceInfo); void checkNetworkDeviceApiV2(const NetworkDeviceInfo &networkDeviceInfo); + void onServiceEntryAdded(const ZeroConfServiceEntry &serviceEntry); + void cleanupPendingReplies(); void finishDiscovery(); diff --git a/goecharger/integrationplugingoecharger.cpp b/goecharger/integrationplugingoecharger.cpp index 58532ccd..92470c57 100644 --- a/goecharger/integrationplugingoecharger.cpp +++ b/goecharger/integrationplugingoecharger.cpp @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2022, nymea GmbH +* Copyright 2013 - 2024, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -28,9 +28,10 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -#include "plugininfo.h" #include "integrationplugingoecharger.h" -#include "network/networkdevicediscovery.h" +#include "plugininfo.h" +#include "goediscovery.h" + #include #include @@ -38,8 +39,6 @@ #include #include -#include "goediscovery.h" - // API documentation: // V1: https://github.com/goecharger/go-eCharger-API-v1 // V2: https://github.com/goecharger/go-eCharger-API-v2 @@ -52,6 +51,12 @@ IntegrationPluginGoECharger::IntegrationPluginGoECharger() void IntegrationPluginGoECharger::init() { connect(this, &IntegrationPlugin::configValueChanged, this, &IntegrationPluginGoECharger::onConfigValueChanged); + + if (!m_serviceBrowser) { + m_serviceBrowser = hardwareManager()->zeroConfController()->createServiceBrowser(); + connect(m_serviceBrowser, &ZeroConfServiceBrowser::serviceEntryAdded, this, &IntegrationPluginGoECharger::onServiceEntryAdded); + } + } void IntegrationPluginGoECharger::discoverThings(ThingDiscoveryInfo *info) @@ -62,7 +67,8 @@ void IntegrationPluginGoECharger::discoverThings(ThingDiscoveryInfo *info) return; } - GoeDiscovery *discovery = new GoeDiscovery(hardwareManager()->networkManager(), hardwareManager()->networkDeviceDiscovery(), this); + GoeDiscovery *discovery = new GoeDiscovery(hardwareManager()->networkManager(), hardwareManager()->networkDeviceDiscovery(), m_serviceBrowser, this); + connect(discovery, &GoeDiscovery::discoveryFinished, discovery, &GoeDiscovery::deleteLater); connect(discovery, &GoeDiscovery::discoveryFinished, info, [=](){ foreach (const GoeDiscovery::Result &result, discovery->discoveryResults()) { @@ -79,7 +85,13 @@ void IntegrationPluginGoECharger::discoverThings(ThingDiscoveryInfo *info) title += " (" + result.manufacturer + ")"; } - QString description = "Serial: " + result.serialNumber + ", V: " + result.firmwareVersion + " - " + result.networkDeviceInfo.address().toString(); + QString description = "Serial: " + result.serialNumber + ", V: " + result.firmwareVersion; + if (result.discoveryMethod == GoeDiscovery::DiscoveryMethodNetwork) { + description.append(" - " + result.networkDeviceInfo.address().toString()); + } else { + description.append(" - " + result.address.toString()); + } + qCDebug(dcGoECharger()) << "-->" << title << description; ThingDescriptor descriptor(goeHomeThingClassId, title, description); ParamList params; @@ -89,7 +101,7 @@ void IntegrationPluginGoECharger::discoverThings(ThingDiscoveryInfo *info) descriptor.setParams(params); // Check if we already have set up this device - Things existingThings = myThings().filterByParam(goeHomeThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress()); + Things existingThings = myThings().filterByParam(goeHomeThingSerialNumberParamTypeId, result.serialNumber); if (existingThings.count() == 1) { qCDebug(dcGoECharger()) << "This wallbox already exists in the system!" << result.networkDeviceInfo; descriptor.setThingId(existingThings.first()->id()); @@ -112,103 +124,110 @@ void IntegrationPluginGoECharger::setupThing(ThingSetupInfo *info) MacAddress macAddress = MacAddress(thing->paramValue(goeHomeThingMacAddressParamTypeId).toString()); if (!macAddress.isValid()) { - qCWarning(dcGoECharger()) << "The configured mac address is not valid" << thing->params(); - info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("The MAC address is not known. Please reconfigure the thing.")); - return; - } - - // Handle reconfigure - if (m_monitors.contains(thing)) - hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing)); - - // Create the monitor - NetworkDeviceMonitor *monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(macAddress); - m_monitors.insert(thing, monitor); - QHostAddress address = getHostAddress(thing); - if (address.isNull()) { - qCWarning(dcGoECharger()) << "Cannot set up go-eCharger. The host address is not known yet. Maybe it will be available in the next run..."; - hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing)); - info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("The host address is not known yet. Trying later again.")); - return; - } - - // Clean up in case the setup gets aborted - connect(info, &ThingSetupInfo::aborted, monitor, [=](){ - if (m_monitors.contains(thing)) { - qCDebug(dcGoECharger()) << "Unregister monitor because setup has been aborted."; + // ZeroConf + QHostAddress address = getHostAddress(thing); + if (address.isNull()) { + qCWarning(dcGoECharger()) << "Cannot set up go-eCharger. The host address is not known yet. Maybe it will be available in the next run..."; hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing)); + info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("The host address is not known yet. Trying later again.")); + return; } - }); - connect(monitor, &NetworkDeviceMonitor::reachableChanged, thing, [=](bool reachable){ - qCDebug(dcGoECharger()) << "Network device monitor reachable changed for" << thing->name() << reachable; - if (reachable && thing->setupComplete() && !thing->stateValue("connected").toBool()) { + setupGoeHome(info); + return; + } else { - // The device is reachable again and we have already set it up. - // Update data and optionally reconfigure the mqtt channel + // Handle reconfigure + if (m_monitors.contains(thing)) + hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing)); - QNetworkReply *reply = hardwareManager()->networkManager()->get(buildStatusRequest(thing, true)); - connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); - connect(reply, &QNetworkReply::finished, thing, [=](){ - if (reply->error() != QNetworkReply::NoError) { - qCWarning(dcGoECharger()) << "HTTP status reply returned error:" << reply->errorString() << reply->readAll(); - return; - } + // Create the monitor + NetworkDeviceMonitor *monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(macAddress); + m_monitors.insert(thing, monitor); + QHostAddress address = getHostAddress(thing); + if (address.isNull()) { + qCWarning(dcGoECharger()) << "Cannot set up go-eCharger. The host address is not known yet. Maybe it will be available in the next run..."; + hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing)); + info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("The host address is not known yet. Trying later again.")); + return; + } - QByteArray data = reply->readAll(); - QJsonParseError error; - QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); - if (error.error != QJsonParseError::NoError) { - qCWarning(dcGoECharger()) << "Failed to parse status data for thing " << thing->name() << qUtf8Printable(data) << error.errorString(); - return; - } + // Clean up in case the setup gets aborted + connect(info, &ThingSetupInfo::aborted, monitor, [=](){ + if (m_monitors.contains(thing)) { + qCDebug(dcGoECharger()) << "Unregister monitor because setup has been aborted."; + hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing)); + } + }); - qCDebug(dcGoECharger()) << "Initial status map" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Compact)); - QVariantMap statusMap = jsonDoc.toVariant().toMap(); + connect(monitor, &NetworkDeviceMonitor::reachableChanged, thing, [=](bool reachable){ + qCDebug(dcGoECharger()) << "Network device monitor reachable changed for" << thing->name() << reachable; + if (reachable && thing->setupComplete() && !thing->stateValue("connected").toBool()) { - ApiVersion apiVersion = getApiVersion(thing); - switch (apiVersion) { - case ApiVersion1: - if (thing->paramValue(goeHomeThingUseMqttParamTypeId).toBool()) { - // Verify mqtt client and set it up - qCDebug(dcGoECharger()) << "Setup using MQTT connection for" << thing; - reconfigureMqttChannelV1(thing, statusMap); - } else { - // Since we are not using mqtt, we are done with the setup, the refresh timer will be configured in post setup - qCDebug(dcGoECharger()) << "Setup using HTTP finished successfully"; - updateV1(thing, statusMap); + // The device is reachable again and we have already set it up. + // Update data and optionally reconfigure the mqtt channel + + QNetworkReply *reply = hardwareManager()->networkManager()->get(buildStatusRequest(thing, true)); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, thing, [=](){ + if (reply->error() != QNetworkReply::NoError) { + qCWarning(dcGoECharger()) << "HTTP status reply returned error:" << reply->errorString() << reply->readAll(); + return; } - break; - case ApiVersion2: - if (thing->paramValue(goeHomeThingUseMqttParamTypeId).toBool()) { - // Verify mqtt client and set it up - qCDebug(dcGoECharger()) << "Setup using MQTT connection for" << thing; - reconfigureMqttChannelV2(thing); - } else { - // Since we are not using mqtt, we are done with the setup, the refresh timer will be configured in post setup - qCDebug(dcGoECharger()) << "Setup using HTTP finished successfully"; - updateV2(thing, statusMap); + + QByteArray data = reply->readAll(); + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) { + qCWarning(dcGoECharger()) << "Failed to parse status data for thing " << thing->name() << qUtf8Printable(data) << error.errorString(); + return; } - break; + + qCDebug(dcGoECharger()) << "Initial status map" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Compact)); + QVariantMap statusMap = jsonDoc.toVariant().toMap(); + + ApiVersion apiVersion = getApiVersion(thing); + switch (apiVersion) { + case ApiVersion1: + if (thing->paramValue(goeHomeThingUseMqttParamTypeId).toBool()) { + // Verify mqtt client and set it up + qCDebug(dcGoECharger()) << "Setup using MQTT connection for" << thing; + reconfigureMqttChannelV1(thing, statusMap); + } else { + // Since we are not using mqtt, we are done with the setup, the refresh timer will be configured in post setup + qCDebug(dcGoECharger()) << "Setup using HTTP finished successfully"; + updateV1(thing, statusMap); + } + break; + case ApiVersion2: + if (thing->paramValue(goeHomeThingUseMqttParamTypeId).toBool()) { + // Verify mqtt client and set it up + qCDebug(dcGoECharger()) << "Setup using MQTT connection for" << thing; + reconfigureMqttChannelV2(thing); + } else { + // Since we are not using mqtt, we are done with the setup, the refresh timer will be configured in post setup + qCDebug(dcGoECharger()) << "Setup using HTTP finished successfully"; + updateV2(thing, statusMap); + } + break; + } + }); + } + }); + + // Wait for the monitor to be ready + if (monitor->reachable()) { + // Thing already reachable...let's finish the setup + setupGoeHome(info); + } else { + qCDebug(dcGoECharger()) << "Wait for the network monitor to get reachable"; + connect(monitor, &NetworkDeviceMonitor::reachableChanged, info, [=](bool reachable){ + if (reachable) { + setupGoeHome(info); } }); } - }); - - // Wait for the monitor to be ready - if (monitor->reachable()) { - // Thing already reachable...let's finish the setup - setupGoeHome(info); - } else { - qCDebug(dcGoECharger()) << "Wait for the network monitor to get reachable"; - connect(monitor, &NetworkDeviceMonitor::reachableChanged, info, [=](bool reachable){ - if (reachable) { - setupGoeHome(info); - } - }); } - - Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8()); } void IntegrationPluginGoECharger::postSetupThing(Thing *thing) @@ -288,7 +307,7 @@ void IntegrationPluginGoECharger::executeAction(ThingActionInfo *info) return; } - if (!thing->stateValue("connected").toBool()) { + if (!thing->stateValue("connected").toBool() || address.isNull()) { qCWarning(dcGoECharger()) << thing << "failed to execute action. The device seems not to be connected."; info->finish(Thing::ThingErrorHardwareNotAvailable); return; @@ -330,7 +349,8 @@ void IntegrationPluginGoECharger::executeAction(ThingActionInfo *info) connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); connect(reply, &QNetworkReply::finished, info, [=](){ if (reply->error() != QNetworkReply::NoError) { - qCWarning(dcGoECharger()) << "Execute action failed for" << thing->name() << "HTTP error:" << reply->errorString() << reply->readAll() << "Request was:" << request.url().toString(); + qCWarning(dcGoECharger()) << "Execute action failed for" << thing->name() << "HTTP error:" << reply->errorString() << reply->readAll() + << "Request was:" << request.url().toString(); info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("The wallbox does not seem to be reachable.")); return; } @@ -339,7 +359,8 @@ void IntegrationPluginGoECharger::executeAction(ThingActionInfo *info) QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); if (error.error != QJsonParseError::NoError) { - qCWarning(dcGoECharger()) << "Execute action failed for" << thing->name() << "Parsing data failed:" << qUtf8Printable(data) << error.errorString() << "Request was:" << request.url().toString(); + qCWarning(dcGoECharger()) << "Execute action failed for" << thing->name() << "Parsing data failed:" << qUtf8Printable(data) << error.errorString() + << "Request was:" << request.url().toString(); info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("The wallbox returned invalid data.")); return; } @@ -369,7 +390,8 @@ void IntegrationPluginGoECharger::executeAction(ThingActionInfo *info) connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); connect(reply, &QNetworkReply::finished, info, [=](){ if (reply->error() != QNetworkReply::NoError) { - qCWarning(dcGoECharger()) << "Execute action failed for" << thing->name() << "HTTP error:" << reply->errorString() << reply->readAll() << "Request was:" << request.url().toString(); + qCWarning(dcGoECharger()) << "Execute action failed for" << thing->name() << "HTTP error:" << reply->errorString() << reply->readAll() + << "Request was:" << request.url().toString(); info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("The wallbox does not seem to be reachable.")); return; } @@ -378,7 +400,8 @@ void IntegrationPluginGoECharger::executeAction(ThingActionInfo *info) QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); if (error.error != QJsonParseError::NoError) { - qCWarning(dcGoECharger()) << "Execute action failed for" << thing->name() << "Failed to parse data" << qUtf8Printable(data) << error.errorString() << "Request was:" << request.url().toString(); + qCWarning(dcGoECharger()) << "Execute action failed for" << thing->name() << "Failed to parse data" << qUtf8Printable(data) << error.errorString() + << "Request was:" << request.url().toString(); info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("The wallbox returned invalid data.")); return; } @@ -408,7 +431,8 @@ void IntegrationPluginGoECharger::executeAction(ThingActionInfo *info) connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); connect(reply, &QNetworkReply::finished, info, [=](){ if (reply->error() != QNetworkReply::NoError) { - qCWarning(dcGoECharger()) << "Execute action failed for" << thing->name() << "HTTP error:" << reply->errorString() << reply->readAll() << "Request was:" << request.url().toString(); + qCWarning(dcGoECharger()) << "Execute action failed for" << thing->name() << "HTTP error:" << reply->errorString() << reply->readAll() + << "Request was:" << request.url().toString(); info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("The wallbox does not seem to be reachable.")); return; } @@ -417,7 +441,8 @@ void IntegrationPluginGoECharger::executeAction(ThingActionInfo *info) QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); if (error.error != QJsonParseError::NoError) { - qCWarning(dcGoECharger()) << "Execute action failed for" << thing->name() << "Failed to parse data" << qUtf8Printable(data) << error.errorString() << "Request was:" << request.url().toString(); + qCWarning(dcGoECharger()) << "Execute action failed for" << thing->name() << "Failed to parse data" << qUtf8Printable(data) << error.errorString() + << "Request was:" << request.url().toString(); info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("The wallbox returned invalid data.")); return; } @@ -551,6 +576,15 @@ QHostAddress IntegrationPluginGoECharger::getHostAddress(Thing *thing) if (m_monitors.contains(thing)) return m_monitors.value(thing)->networkDeviceInfo().address(); + foreach (const ZeroConfServiceEntry &serviceEntry, m_serviceBrowser->serviceEntries()) { + if (GoeDiscovery::isGoeCharger(serviceEntry)) { + QString serial = serviceEntry.txt("serial"); + if (thing->paramValue(goeHomeThingSerialNumberParamTypeId).toString() == serial) { + return serviceEntry.hostAddress(); + } + } + } + return QHostAddress(); } @@ -1551,6 +1585,14 @@ void IntegrationPluginGoECharger::onConfigValueChanged(const ParamTypeId ¶mT } } +void IntegrationPluginGoECharger::onServiceEntryAdded(const ZeroConfServiceEntry &serviceEntry) +{ + if (GoeDiscovery::isGoeCharger(serviceEntry)) { + qCDebug(dcGoECharger()) << "Found ZeroConf go-eCharger:" << serviceEntry; + + } +} + void IntegrationPluginGoECharger::onMqttClientV1Connected(MqttChannel *channel) { diff --git a/goecharger/integrationplugingoecharger.h b/goecharger/integrationplugingoecharger.h index 5ffb76db..80e3b7f2 100644 --- a/goecharger/integrationplugingoecharger.h +++ b/goecharger/integrationplugingoecharger.h @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2022, nymea GmbH +* Copyright 2013 - 2024, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -40,6 +40,8 @@ #include #include +#include + #include "extern-plugininfo.h" class IntegrationPluginGoECharger: public IntegrationPlugin @@ -112,6 +114,8 @@ private: QHash m_pendingReplies; QHash m_monitors; + ZeroConfServiceBrowser *m_serviceBrowser = nullptr; + // General methods void setupGoeHome(ThingSetupInfo *info); QNetworkRequest buildStatusRequest(Thing *thing, bool fullStatus = false); @@ -136,6 +140,9 @@ private slots: void onConfigValueChanged(const ParamTypeId ¶mTypeId, const QVariant &value); + // ZeroConf + void onServiceEntryAdded(const ZeroConfServiceEntry &serviceEntry); + // API V1 void onMqttClientV1Connected(MqttChannel* channel); void onMqttClientV1Disconnected(MqttChannel* channel);