From 4221bf3373af46976de5390e5d5e7141d237d9c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 18 Dec 2024 15:53:42 +0100 Subject: [PATCH] Stiebeleltron: Update to the networkdevice interface --- .../integrationpluginstiebeleltron.cpp | 217 +++++++++++------- .../integrationpluginstiebeleltron.h | 6 +- .../integrationpluginstiebeleltron.json | 14 +- 3 files changed, 148 insertions(+), 89 deletions(-) diff --git a/stiebeleltron/integrationpluginstiebeleltron.cpp b/stiebeleltron/integrationpluginstiebeleltron.cpp index 9d1705de..b09c8e37 100644 --- a/stiebeleltron/integrationpluginstiebeleltron.cpp +++ b/stiebeleltron/integrationpluginstiebeleltron.cpp @@ -1,6 +1,7 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Copyright 2013 - 2021, nymea GmbH, Consolinno Energy GmbH, L. Heizinger + * Copyright 2013 - 2024, nymea GmbH + * 2021, Consolinno Energy GmbH, L. Heizinger * Contact: contact@nymea.io * * This file is part of nymea. @@ -36,11 +37,11 @@ IntegrationPluginStiebelEltron::IntegrationPluginStiebelEltron() {} -void IntegrationPluginStiebelEltron::discoverThings(ThingDiscoveryInfo *info) { +void IntegrationPluginStiebelEltron::discoverThings(ThingDiscoveryInfo *info) +{ if (!hardwareManager()->networkDeviceDiscovery()->available()) { qCWarning(dcStiebelEltron()) << "The network discovery is not available on this platform."; - info->finish(Thing::ThingErrorUnsupportedFeature, - QT_TR_NOOP("The network device discovery is not available.")); + info->finish(Thing::ThingErrorUnsupportedFeature, QT_TR_NOOP("The network device discovery is not available.")); return; } @@ -51,34 +52,46 @@ void IntegrationPluginStiebelEltron::discoverThings(ThingDiscoveryInfo *info) { qCDebug(dcStiebelEltron()) << "Found" << networkDeviceInfo; QString title; - if (networkDeviceInfo.hostName().isEmpty()) { - title = networkDeviceInfo.address().toString(); - } else { - if (!networkDeviceInfo.hostName().contains("StiebelEltronISG")) continue; - title = networkDeviceInfo.hostName() + " (" + networkDeviceInfo.address().toString() + ")"; - } - QString description; - if (networkDeviceInfo.macAddressManufacturer().isEmpty()) { - description = networkDeviceInfo.macAddress(); - } else { - description = - networkDeviceInfo.macAddress() + " (" + networkDeviceInfo.macAddressManufacturer() + ")"; + MacAddressInfo macInfo; + + switch (networkDeviceInfo.monitorMode()) { + case NetworkDeviceInfo::MonitorModeMac: + macInfo = networkDeviceInfo.macAddressInfos().constFirst(); + description = networkDeviceInfo.address().toString(); + if (!macInfo.vendorName().isEmpty()) + description += " - " + networkDeviceInfo.macAddressInfos().constFirst().vendorName(); + + if (networkDeviceInfo.hostName().isEmpty()) { + title = macInfo.macAddress().toString(); + } else { + title = networkDeviceInfo.hostName() + " (" + macInfo.macAddress().toString() + ")"; + } + + break; + case NetworkDeviceInfo::MonitorModeHostName: + title = networkDeviceInfo.hostName(); + description = networkDeviceInfo.address().toString(); + break; + case NetworkDeviceInfo::MonitorModeIp: + title = "Network device " + networkDeviceInfo.address().toString(); + description = "Interface: " + networkDeviceInfo.networkInterface().name(); + break; } ThingDescriptor descriptor(stiebelEltronThingClassId, title, description); + ParamList params; - params << Param(stiebelEltronThingIpAddressParamTypeId, networkDeviceInfo.address().toString()); - params << Param(stiebelEltronThingMacAddressParamTypeId, networkDeviceInfo.macAddress()); + params << Param(stiebelEltronThingAddressParamTypeId, networkDeviceInfo.thingParamValueAddress()); + params << Param(stiebelEltronThingMacAddressParamTypeId, networkDeviceInfo.thingParamValueMacAddress()); + params << Param(stiebelEltronThingHostNameParamTypeId, networkDeviceInfo.thingParamValueHostName()); descriptor.setParams(params); // Check if we already have set up this device - Things existingThings = myThings().filterByParam(stiebelEltronThingMacAddressParamTypeId, - networkDeviceInfo.macAddress()); - if (existingThings.count() == 1) { - qCDebug(dcStiebelEltron()) - << "This connection already exists in the system:" << networkDeviceInfo; - descriptor.setThingId(existingThings.first()->id()); + Thing *existingThing = myThings().findByParams(params); + if (existingThing) { + qCDebug(dcStiebelEltron()) << "This thing already exists in the system:" << existingThing << existingThing->params(); + descriptor.setThingId(existingThing->id()); } info->addThingDescriptor(descriptor); @@ -88,19 +101,57 @@ void IntegrationPluginStiebelEltron::discoverThings(ThingDiscoveryInfo *info) { }); } -void IntegrationPluginStiebelEltron::startMonitoringAutoThings() {} - -void IntegrationPluginStiebelEltron::setupThing(ThingSetupInfo *info) { +void IntegrationPluginStiebelEltron::setupThing(ThingSetupInfo *info) +{ Thing *thing = info->thing(); qCDebug(dcStiebelEltron()) << "Setup" << thing << thing->params(); if (thing->thingClassId() == stiebelEltronThingClassId) { - QHostAddress address(thing->paramValue(stiebelEltronThingIpAddressParamTypeId).toString()); + + // Create the monitor + NetworkDeviceMonitor *monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(thing); + if (!monitor) { + qCWarning(dcStiebelEltron()) << "Unable to register monitor with the given params" << thing->params(); + info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("Unable to set up the connection with this configuration, please reconfigure the connection.")); + return; + } + + m_monitors.insert(thing, monitor); + + connect(info, &ThingSetupInfo::aborted, monitor, [=](){ + // Clean up in case the setup gets aborted + if (m_monitors.contains(thing)) { + qCDebug(dcStiebelEltron()) << "Unregister monitor because the setup has been aborted."; + hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing)); + } + }); + + QHostAddress address = m_monitors.value(thing)->networkDeviceInfo().address(); quint16 port = thing->paramValue(stiebelEltronThingPortParamTypeId).toUInt(); quint16 slaveId = thing->paramValue(stiebelEltronThingSlaveIdParamTypeId).toUInt(); - StiebelEltronModbusTcpConnection *connection = - new StiebelEltronModbusTcpConnection(address, port, slaveId, this); + StiebelEltronModbusTcpConnection *connection = new StiebelEltronModbusTcpConnection(address, port, slaveId, this); + + connect(info, &ThingSetupInfo::aborted, connection, [connection](){ + connection->disconnectDevice(); + connection->deleteLater(); + }); + + // Reconnect on monitor reachable changed + connect(monitor, &NetworkDeviceMonitor::reachableChanged, thing, [=](bool reachable){ + qCDebug(dcStiebelEltron()) << "Network device monitor reachable changed for" << thing->name() << reachable; + if (!thing->setupComplete()) + return; + + if (reachable && !thing->stateValue("connected").toBool()) { + connection->modbusTcpMaster()->setHostAddress(monitor->networkDeviceInfo().address()); + connection->reconnectDevice(); + } else if (!reachable) { + // Note: We disable autoreconnect explicitly and we will + // connect the device once the monitor says it is reachable again + connection->disconnectDevice(); + } + }); connect(connection, &StiebelEltronModbusTcpConnection::reachableChanged, thing, [thing, connection](bool reachable) { @@ -115,28 +166,28 @@ void IntegrationPluginStiebelEltron::setupThing(ThingSetupInfo *info) { connect(connection, &StiebelEltronModbusTcpConnection::outdoorTemperatureChanged, thing, [thing](float outdoorTemperature) { qCDebug(dcStiebelEltron()) - << thing << "outdoor temperature changed" << outdoorTemperature << "°C"; + << thing << "outdoor temperature changed" << outdoorTemperature << "°C"; thing->setStateValue(stiebelEltronOutdoorTemperatureStateTypeId, outdoorTemperature); }); connect(connection, &StiebelEltronModbusTcpConnection::flowTemperatureChanged, thing, [thing](float flowTemperature) { qCDebug(dcStiebelEltron()) - << thing << "flow temperature changed" << flowTemperature << "°C"; + << thing << "flow temperature changed" << flowTemperature << "°C"; thing->setStateValue(stiebelEltronFlowTemperatureStateTypeId, flowTemperature); }); connect(connection, &StiebelEltronModbusTcpConnection::hotWaterTemperatureChanged, thing, [thing](float hotWaterTemperature) { qCDebug(dcStiebelEltron()) - << thing << "hot water temperature changed" << hotWaterTemperature << "°C"; + << thing << "hot water temperature changed" << hotWaterTemperature << "°C"; thing->setStateValue(stiebelEltronHotWaterTemperatureStateTypeId, hotWaterTemperature); }); connect(connection, &StiebelEltronModbusTcpConnection::storageTankTemperatureChanged, thing, [thing](float storageTankTemperature) { qCDebug(dcStiebelEltron()) - << thing << "Storage tank temperature changed" << storageTankTemperature << "°C"; + << thing << "Storage tank temperature changed" << storageTankTemperature << "°C"; thing->setStateValue(stiebelEltronStorageTankTemperatureStateTypeId, storageTankTemperature); }); @@ -144,7 +195,7 @@ void IntegrationPluginStiebelEltron::setupThing(ThingSetupInfo *info) { connect(connection, &StiebelEltronModbusTcpConnection::returnTemperatureChanged, thing, [thing](float returnTemperature) { qCDebug(dcStiebelEltron()) - << thing << "return temperature changed" << returnTemperature << "°C"; + << thing << "return temperature changed" << returnTemperature << "°C"; thing->setStateValue(stiebelEltronReturnTemperatureStateTypeId, returnTemperature); }); @@ -192,24 +243,24 @@ void IntegrationPluginStiebelEltron::setupThing(ThingSetupInfo *info) { [thing](StiebelEltronModbusTcpConnection::OperatingMode operatingMode) { qCDebug(dcStiebelEltron()) << thing << "operating mode changed " << operatingMode; switch (operatingMode) { - case StiebelEltronModbusTcpConnection::OperatingModeEmergency: - thing->setStateValue(stiebelEltronOperatingModeStateTypeId, "Emergency"); - break; - case StiebelEltronModbusTcpConnection::OperatingModeStandby: - thing->setStateValue(stiebelEltronOperatingModeStateTypeId, "Standby"); - break; - case StiebelEltronModbusTcpConnection::OperatingModeProgram: - thing->setStateValue(stiebelEltronOperatingModeStateTypeId, "Program"); - break; - case StiebelEltronModbusTcpConnection::OperatingModeComfort: - thing->setStateValue(stiebelEltronOperatingModeStateTypeId, "Comfort"); - break; - case StiebelEltronModbusTcpConnection::OperatingModeEco: - thing->setStateValue(stiebelEltronOperatingModeStateTypeId, "Eco"); - break; - case StiebelEltronModbusTcpConnection::OperatingModeHotWater: - thing->setStateValue(stiebelEltronOperatingModeStateTypeId, "Hot water"); - break; + case StiebelEltronModbusTcpConnection::OperatingModeEmergency: + thing->setStateValue(stiebelEltronOperatingModeStateTypeId, "Emergency"); + break; + case StiebelEltronModbusTcpConnection::OperatingModeStandby: + thing->setStateValue(stiebelEltronOperatingModeStateTypeId, "Standby"); + break; + case StiebelEltronModbusTcpConnection::OperatingModeProgram: + thing->setStateValue(stiebelEltronOperatingModeStateTypeId, "Program"); + break; + case StiebelEltronModbusTcpConnection::OperatingModeComfort: + thing->setStateValue(stiebelEltronOperatingModeStateTypeId, "Comfort"); + break; + case StiebelEltronModbusTcpConnection::OperatingModeEco: + thing->setStateValue(stiebelEltronOperatingModeStateTypeId, "Eco"); + break; + case StiebelEltronModbusTcpConnection::OperatingModeHotWater: + thing->setStateValue(stiebelEltronOperatingModeStateTypeId, "Hot water"); + break; } }); @@ -234,18 +285,18 @@ void IntegrationPluginStiebelEltron::setupThing(ThingSetupInfo *info) { [thing](StiebelEltronModbusTcpConnection::SmartGridState smartGridState) { qCDebug(dcStiebelEltron()) << thing << "SG Ready mode changed" << smartGridState; switch (smartGridState) { - case StiebelEltronModbusTcpConnection::SmartGridStateModeOne: - thing->setStateValue(stiebelEltronSgReadyModeStateTypeId, "Off"); - break; - case StiebelEltronModbusTcpConnection::SmartGridStateModeTwo: - thing->setStateValue(stiebelEltronSgReadyModeStateTypeId, "Low"); - break; - case StiebelEltronModbusTcpConnection::SmartGridStateModeThree: - thing->setStateValue(stiebelEltronSgReadyModeStateTypeId, "Standard"); - break; - case StiebelEltronModbusTcpConnection::SmartGridStateModeFour: - thing->setStateValue(stiebelEltronSgReadyModeStateTypeId, "High"); - break; + case StiebelEltronModbusTcpConnection::SmartGridStateModeOne: + thing->setStateValue(stiebelEltronSgReadyModeStateTypeId, "Off"); + break; + case StiebelEltronModbusTcpConnection::SmartGridStateModeTwo: + thing->setStateValue(stiebelEltronSgReadyModeStateTypeId, "Low"); + break; + case StiebelEltronModbusTcpConnection::SmartGridStateModeThree: + thing->setStateValue(stiebelEltronSgReadyModeStateTypeId, "Standard"); + break; + case StiebelEltronModbusTcpConnection::SmartGridStateModeFour: + thing->setStateValue(stiebelEltronSgReadyModeStateTypeId, "High"); + break; } }); connect(connection, &StiebelEltronModbusTcpConnection::sgReadyActiveChanged, thing, @@ -261,7 +312,8 @@ void IntegrationPluginStiebelEltron::setupThing(ThingSetupInfo *info) { } } -void IntegrationPluginStiebelEltron::postSetupThing(Thing *thing) { +void IntegrationPluginStiebelEltron::postSetupThing(Thing *thing) +{ if (thing->thingClassId() == stiebelEltronThingClassId) { if (!m_pluginTimer) { qCDebug(dcStiebelEltron()) << "Starting plugin timer..."; @@ -279,10 +331,13 @@ void IntegrationPluginStiebelEltron::postSetupThing(Thing *thing) { } } -void IntegrationPluginStiebelEltron::thingRemoved(Thing *thing) { - if (thing->thingClassId() == stiebelEltronThingClassId && m_connections.contains(thing)) { +void IntegrationPluginStiebelEltron::thingRemoved(Thing *thing) +{ + if (thing->thingClassId() == stiebelEltronThingClassId && m_connections.contains(thing)) m_connections.take(thing)->deleteLater(); - } + + if (m_monitors.contains(thing)) + hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing)); if (myThings().isEmpty() && m_pluginTimer) { hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); @@ -290,22 +345,17 @@ void IntegrationPluginStiebelEltron::thingRemoved(Thing *thing) { } } -void IntegrationPluginStiebelEltron::executeAction(ThingActionInfo *info) { +void IntegrationPluginStiebelEltron::executeAction(ThingActionInfo *info) +{ Thing *thing = info->thing(); StiebelEltronModbusTcpConnection *connection = m_connections.value(thing); if (!connection->reachable()) { - qCWarning(dcStiebelEltron()) << "Could not execute action. The modbus connection is currently " - "not available."; + qCWarning(dcStiebelEltron()) << "Could not execute action. The modbus connection is currently not available."; info->finish(Thing::ThingErrorHardwareNotAvailable); return; } - // Got this from StiebelEltron plugin, not sure if necessary - if (thing->thingClassId() != stiebelEltronThingClassId) { - info->finish(Thing::ThingErrorNoError); - } - if (info->action().actionTypeId() == stiebelEltronSgReadyActiveActionTypeId) { bool sgReadyActiveBool = info->action().paramValue(stiebelEltronSgReadyActiveActionSgReadyActiveParamTypeId).toBool(); @@ -315,8 +365,7 @@ void IntegrationPluginStiebelEltron::executeAction(ThingActionInfo *info) { QModbusReply *reply = connection->setSgReadyActive(sgReadyActiveBool); if (!reply) { - qCWarning(dcStiebelEltron()) << "Execute action failed because the " - "reply could not be created."; + qCWarning(dcStiebelEltron()) << "Execute action failed because the reply could not be created."; info->finish(Thing::ThingErrorHardwareFailure); return; } @@ -325,7 +374,7 @@ void IntegrationPluginStiebelEltron::executeAction(ThingActionInfo *info) { connect(reply, &QModbusReply::finished, info, [info, reply, sgReadyActiveBool] { if (reply->error() != QModbusDevice::NoError) { qCWarning(dcStiebelEltron()) - << "Set SG ready activation finished with error" << reply->errorString(); + << "Set SG ready activation finished with error" << reply->errorString(); info->finish(Thing::ThingErrorHardwareFailure); return; } @@ -338,7 +387,7 @@ void IntegrationPluginStiebelEltron::executeAction(ThingActionInfo *info) { connect(reply, &QModbusReply::errorOccurred, this, [reply](QModbusDevice::Error error) { qCWarning(dcStiebelEltron()) - << "Modbus reply error occurred while execute action" << error << reply->errorString(); + << "Modbus reply error occurred while execute action" << error << reply->errorString(); emit reply->finished(); // To make sure it will be deleted }); } else if (info->action().actionTypeId() == stiebelEltronSgReadyModeActionTypeId) { @@ -357,7 +406,7 @@ void IntegrationPluginStiebelEltron::executeAction(ThingActionInfo *info) { sgReadyState = StiebelEltronModbusTcpConnection::SmartGridStateModeFour; } else { qCWarning(dcStiebelEltron()) - << "Failed to set SG Ready mode. An unknown SG Ready mode was passed: " << sgReadyModeString; + << "Failed to set SG Ready mode. An unknown SG Ready mode was passed: " << sgReadyModeString; info->finish(Thing::ThingErrorHardwareFailure); // TODO better matching error type? return; } @@ -374,7 +423,7 @@ void IntegrationPluginStiebelEltron::executeAction(ThingActionInfo *info) { connect(reply, &QModbusReply::finished, info, [info, reply, sgReadyModeString] { if (reply->error() != QModbusDevice::NoError) { qCWarning(dcStiebelEltron()) - << "Set SG ready mode finished with error" << reply->errorString(); + << "Set SG ready mode finished with error" << reply->errorString(); info->finish(Thing::ThingErrorHardwareFailure); return; } @@ -387,7 +436,7 @@ void IntegrationPluginStiebelEltron::executeAction(ThingActionInfo *info) { connect(reply, &QModbusReply::errorOccurred, this, [reply](QModbusDevice::Error error) { qCWarning(dcStiebelEltron()) - << "Modbus reply error occurred while execute action" << error << reply->errorString(); + << "Modbus reply error occurred while execute action" << error << reply->errorString(); emit reply->finished(); // To make sure it will be deleted }); } diff --git a/stiebeleltron/integrationpluginstiebeleltron.h b/stiebeleltron/integrationpluginstiebeleltron.h index 260253d6..5db06bbe 100644 --- a/stiebeleltron/integrationpluginstiebeleltron.h +++ b/stiebeleltron/integrationpluginstiebeleltron.h @@ -1,6 +1,7 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2021, nymea GmbH, Consolinno Energy GmbH, L. Heizinger +* Copyright 2013 - 2024, nymea GmbH +* 2021, Consolinno Energy GmbH, L. Heizinger * Contact: contact@nymea.io * * This file is part of nymea. @@ -33,6 +34,7 @@ #include #include +#include #include "stiebeleltronmodbustcpconnection.h" @@ -47,7 +49,6 @@ class IntegrationPluginStiebelEltron: public IntegrationPlugin explicit IntegrationPluginStiebelEltron(); void discoverThings(ThingDiscoveryInfo *info) override; - void startMonitoringAutoThings() override; void setupThing(ThingSetupInfo *info) override; void postSetupThing(Thing *thing) override; void thingRemoved(Thing *thing) override; @@ -56,6 +57,7 @@ class IntegrationPluginStiebelEltron: public IntegrationPlugin private: PluginTimer *m_pluginTimer = nullptr; + QHash m_monitors; QHash m_connections; diff --git a/stiebeleltron/integrationpluginstiebeleltron.json b/stiebeleltron/integrationpluginstiebeleltron.json index 299a19a2..c75e8f84 100644 --- a/stiebeleltron/integrationpluginstiebeleltron.json +++ b/stiebeleltron/integrationpluginstiebeleltron.json @@ -13,15 +13,23 @@ "displayName": "Stiebel Eltron Heatpump", "id": "e02ecf61-7d28-43c2-b87e-e7e98a48fbfd", "createMethods": ["discovery", "user"], - "interfaces": ["smartgridheatpump", "connectable"], + "interfaces": ["smartgridheatpump", "connectable", "networkdevice"], "paramTypes": [ { "id": "47d221fa-f6d2-400e-b80f-bb90abccb72c", - "name": "ipAddress", + "name": "address", "displayName": "IP address", "type": "QString", "inputType": "IPv4Address", - "defaultValue": "127.0.0.1" + "defaultValue": "" + }, + { + "id": "f39a2708-a0f2-419f-a01d-4e600dabe664", + "name": "hostName", + "displayName": "Host name", + "type": "QString", + "inputType": "TextLine", + "defaultValue": "" }, { "id": "05cd59b8-3068-460f-b0d2-6d49f27458df",