Merge PR #200: Stiebeleltron: Update to the networkdevice interface

This commit is contained in:
jenkins 2025-03-30 20:37:28 +02:00
commit be3c6aa5ca
3 changed files with 148 additions and 89 deletions

View File

@ -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 * Contact: contact@nymea.io
* *
* This file is part of nymea. * This file is part of nymea.
@ -36,11 +37,11 @@
IntegrationPluginStiebelEltron::IntegrationPluginStiebelEltron() {} IntegrationPluginStiebelEltron::IntegrationPluginStiebelEltron() {}
void IntegrationPluginStiebelEltron::discoverThings(ThingDiscoveryInfo *info) { void IntegrationPluginStiebelEltron::discoverThings(ThingDiscoveryInfo *info)
{
if (!hardwareManager()->networkDeviceDiscovery()->available()) { if (!hardwareManager()->networkDeviceDiscovery()->available()) {
qCWarning(dcStiebelEltron()) << "The network discovery is not available on this platform."; qCWarning(dcStiebelEltron()) << "The network discovery is not available on this platform.";
info->finish(Thing::ThingErrorUnsupportedFeature, info->finish(Thing::ThingErrorUnsupportedFeature, QT_TR_NOOP("The network device discovery is not available."));
QT_TR_NOOP("The network device discovery is not available."));
return; return;
} }
@ -51,34 +52,46 @@ void IntegrationPluginStiebelEltron::discoverThings(ThingDiscoveryInfo *info) {
qCDebug(dcStiebelEltron()) << "Found" << networkDeviceInfo; qCDebug(dcStiebelEltron()) << "Found" << networkDeviceInfo;
QString title; QString title;
QString description;
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()) { if (networkDeviceInfo.hostName().isEmpty()) {
title = networkDeviceInfo.address().toString(); title = macInfo.macAddress().toString();
} else { } else {
if (!networkDeviceInfo.hostName().contains("StiebelEltronISG")) continue; title = networkDeviceInfo.hostName() + " (" + macInfo.macAddress().toString() + ")";
title = networkDeviceInfo.hostName() + " (" + networkDeviceInfo.address().toString() + ")";
} }
QString description; break;
if (networkDeviceInfo.macAddressManufacturer().isEmpty()) { case NetworkDeviceInfo::MonitorModeHostName:
description = networkDeviceInfo.macAddress(); title = networkDeviceInfo.hostName();
} else { description = networkDeviceInfo.address().toString();
description = break;
networkDeviceInfo.macAddress() + " (" + networkDeviceInfo.macAddressManufacturer() + ")"; case NetworkDeviceInfo::MonitorModeIp:
title = "Network device " + networkDeviceInfo.address().toString();
description = "Interface: " + networkDeviceInfo.networkInterface().name();
break;
} }
ThingDescriptor descriptor(stiebelEltronThingClassId, title, description); ThingDescriptor descriptor(stiebelEltronThingClassId, title, description);
ParamList params; ParamList params;
params << Param(stiebelEltronThingIpAddressParamTypeId, networkDeviceInfo.address().toString()); params << Param(stiebelEltronThingAddressParamTypeId, networkDeviceInfo.thingParamValueAddress());
params << Param(stiebelEltronThingMacAddressParamTypeId, networkDeviceInfo.macAddress()); params << Param(stiebelEltronThingMacAddressParamTypeId, networkDeviceInfo.thingParamValueMacAddress());
params << Param(stiebelEltronThingHostNameParamTypeId, networkDeviceInfo.thingParamValueHostName());
descriptor.setParams(params); descriptor.setParams(params);
// Check if we already have set up this device // Check if we already have set up this device
Things existingThings = myThings().filterByParam(stiebelEltronThingMacAddressParamTypeId, Thing *existingThing = myThings().findByParams(params);
networkDeviceInfo.macAddress()); if (existingThing) {
if (existingThings.count() == 1) { qCDebug(dcStiebelEltron()) << "This thing already exists in the system:" << existingThing << existingThing->params();
qCDebug(dcStiebelEltron()) descriptor.setThingId(existingThing->id());
<< "This connection already exists in the system:" << networkDeviceInfo;
descriptor.setThingId(existingThings.first()->id());
} }
info->addThingDescriptor(descriptor); 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(); Thing *thing = info->thing();
qCDebug(dcStiebelEltron()) << "Setup" << thing << thing->params(); qCDebug(dcStiebelEltron()) << "Setup" << thing << thing->params();
if (thing->thingClassId() == stiebelEltronThingClassId) { 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 port = thing->paramValue(stiebelEltronThingPortParamTypeId).toUInt();
quint16 slaveId = thing->paramValue(stiebelEltronThingSlaveIdParamTypeId).toUInt(); quint16 slaveId = thing->paramValue(stiebelEltronThingSlaveIdParamTypeId).toUInt();
StiebelEltronModbusTcpConnection *connection = StiebelEltronModbusTcpConnection *connection = new StiebelEltronModbusTcpConnection(address, port, slaveId, this);
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, connect(connection, &StiebelEltronModbusTcpConnection::reachableChanged, thing,
[thing, connection](bool reachable) { [thing, connection](bool reachable) {
@ -261,7 +312,8 @@ void IntegrationPluginStiebelEltron::setupThing(ThingSetupInfo *info) {
} }
} }
void IntegrationPluginStiebelEltron::postSetupThing(Thing *thing) { void IntegrationPluginStiebelEltron::postSetupThing(Thing *thing)
{
if (thing->thingClassId() == stiebelEltronThingClassId) { if (thing->thingClassId() == stiebelEltronThingClassId) {
if (!m_pluginTimer) { if (!m_pluginTimer) {
qCDebug(dcStiebelEltron()) << "Starting plugin timer..."; qCDebug(dcStiebelEltron()) << "Starting plugin timer...";
@ -279,10 +331,13 @@ void IntegrationPluginStiebelEltron::postSetupThing(Thing *thing) {
} }
} }
void IntegrationPluginStiebelEltron::thingRemoved(Thing *thing) { void IntegrationPluginStiebelEltron::thingRemoved(Thing *thing)
if (thing->thingClassId() == stiebelEltronThingClassId && m_connections.contains(thing)) { {
if (thing->thingClassId() == stiebelEltronThingClassId && m_connections.contains(thing))
m_connections.take(thing)->deleteLater(); m_connections.take(thing)->deleteLater();
}
if (m_monitors.contains(thing))
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
if (myThings().isEmpty() && m_pluginTimer) { if (myThings().isEmpty() && m_pluginTimer) {
hardwareManager()->pluginTimerManager()->unregisterTimer(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(); Thing *thing = info->thing();
StiebelEltronModbusTcpConnection *connection = m_connections.value(thing); StiebelEltronModbusTcpConnection *connection = m_connections.value(thing);
if (!connection->reachable()) { if (!connection->reachable()) {
qCWarning(dcStiebelEltron()) << "Could not execute action. The modbus connection is currently " qCWarning(dcStiebelEltron()) << "Could not execute action. The modbus connection is currently not available.";
"not available.";
info->finish(Thing::ThingErrorHardwareNotAvailable); info->finish(Thing::ThingErrorHardwareNotAvailable);
return; return;
} }
// Got this from StiebelEltron plugin, not sure if necessary
if (thing->thingClassId() != stiebelEltronThingClassId) {
info->finish(Thing::ThingErrorNoError);
}
if (info->action().actionTypeId() == stiebelEltronSgReadyActiveActionTypeId) { if (info->action().actionTypeId() == stiebelEltronSgReadyActiveActionTypeId) {
bool sgReadyActiveBool = bool sgReadyActiveBool =
info->action().paramValue(stiebelEltronSgReadyActiveActionSgReadyActiveParamTypeId).toBool(); info->action().paramValue(stiebelEltronSgReadyActiveActionSgReadyActiveParamTypeId).toBool();
@ -315,8 +365,7 @@ void IntegrationPluginStiebelEltron::executeAction(ThingActionInfo *info) {
QModbusReply *reply = connection->setSgReadyActive(sgReadyActiveBool); QModbusReply *reply = connection->setSgReadyActive(sgReadyActiveBool);
if (!reply) { if (!reply) {
qCWarning(dcStiebelEltron()) << "Execute action failed because the " qCWarning(dcStiebelEltron()) << "Execute action failed because the reply could not be created.";
"reply could not be created.";
info->finish(Thing::ThingErrorHardwareFailure); info->finish(Thing::ThingErrorHardwareFailure);
return; return;
} }

View File

@ -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 * Contact: contact@nymea.io
* *
* This file is part of nymea. * This file is part of nymea.
@ -33,6 +34,7 @@
#include <plugintimer.h> #include <plugintimer.h>
#include <integrations/integrationplugin.h> #include <integrations/integrationplugin.h>
#include <network/networkdevicemonitor.h>
#include "stiebeleltronmodbustcpconnection.h" #include "stiebeleltronmodbustcpconnection.h"
@ -47,7 +49,6 @@ public:
explicit IntegrationPluginStiebelEltron(); explicit IntegrationPluginStiebelEltron();
void discoverThings(ThingDiscoveryInfo *info) override; void discoverThings(ThingDiscoveryInfo *info) override;
void startMonitoringAutoThings() override;
void setupThing(ThingSetupInfo *info) override; void setupThing(ThingSetupInfo *info) override;
void postSetupThing(Thing *thing) override; void postSetupThing(Thing *thing) override;
void thingRemoved(Thing *thing) override; void thingRemoved(Thing *thing) override;
@ -56,6 +57,7 @@ public:
private: private:
PluginTimer *m_pluginTimer = nullptr; PluginTimer *m_pluginTimer = nullptr;
QHash<Thing *, NetworkDeviceMonitor *> m_monitors;
QHash<Thing *, StiebelEltronModbusTcpConnection *> m_connections; QHash<Thing *, StiebelEltronModbusTcpConnection *> m_connections;

View File

@ -13,15 +13,23 @@
"displayName": "Stiebel Eltron Heatpump", "displayName": "Stiebel Eltron Heatpump",
"id": "e02ecf61-7d28-43c2-b87e-e7e98a48fbfd", "id": "e02ecf61-7d28-43c2-b87e-e7e98a48fbfd",
"createMethods": ["discovery", "user"], "createMethods": ["discovery", "user"],
"interfaces": ["smartgridheatpump", "connectable"], "interfaces": ["smartgridheatpump", "connectable", "networkdevice"],
"paramTypes": [ "paramTypes": [
{ {
"id": "47d221fa-f6d2-400e-b80f-bb90abccb72c", "id": "47d221fa-f6d2-400e-b80f-bb90abccb72c",
"name": "ipAddress", "name": "address",
"displayName": "IP address", "displayName": "IP address",
"type": "QString", "type": "QString",
"inputType": "IPv4Address", "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", "id": "05cd59b8-3068-460f-b0d2-6d49f27458df",