From d838bce331379c8b703e3adc1fcd2fcccc05c276 Mon Sep 17 00:00:00 2001 From: Patrick Schurig Date: Fri, 12 Jun 2026 17:13:37 +0200 Subject: [PATCH] fix(v2c): set host address before connectDevice() in setupThing() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug: ModbusTcpMaster: device ':502' ConnectionError when adding a trydan by manual IP (createMethod "user") or after nymead restart before the NetworkDeviceMonitor completes its ARP scan. Root cause: TrydanModbusTcpMaster was constructed with monitor->networkDeviceInfo().address(), which is null at first registerMonitor() — the monitor only populates its cache after an asynchronous ARP/mDNS resolution. connectDevice() then ran against QHostAddress() → ":502". Fix: - Read the initial IP from the stored address param (always set at discovery or user-entry) and use it as the fallback when the monitor has not yet resolved the address. - Guard the networkDeviceInfoChanged callback: only call setHostAddress() when the new address is non-null (the monitor emits transient null info during its internal refresh cycles). Order is now: create master with valid address → wire callbacks → connectDevice(). The monitor update path remains for DHCP renewals. Co-Authored-By: Claude Sonnet 4.6 --- v2c/integrationpluginv2c.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/v2c/integrationpluginv2c.cpp b/v2c/integrationpluginv2c.cpp index 0c428e5..fba02de 100644 --- a/v2c/integrationpluginv2c.cpp +++ b/v2c/integrationpluginv2c.cpp @@ -68,13 +68,23 @@ void IntegrationPluginV2c::setupThing(ThingSetupInfo *info) const quint16 port = static_cast(thing->paramValue(trydanThingPortParamTypeId).toUInt()); const quint16 slaveId = 1; // V2C Trydan always uses unit ID 1 - TrydanModbusTcpMaster *master = new TrydanModbusTcpMaster( - monitor->networkDeviceInfo().address(), port, slaveId, thing); + // Prefer the monitor's cached address (ARP/mDNS already resolved) but fall back + // to the stored address param. The monitor has a null address at first + // registerMonitor() when the device was added by IP (createMethod "user") or when + // nymead restarts before the monitor's ARP scan completes — connecting to a null + // QHostAddress would produce "device ':502'" and fail immediately. + QHostAddress address = monitor->networkDeviceInfo().address(); + if (address.isNull()) + address = QHostAddress(thing->paramValue(trydanThingAddressParamTypeId).toString()); - // Update target IP if the device gets a new DHCP lease. + TrydanModbusTcpMaster *master = new TrydanModbusTcpMaster(address, port, slaveId, thing); + + // Update target IP when DHCP renews. Guard against transient null info that + // the monitor can emit during its own internal refresh cycles. connect(monitor, &NetworkDeviceMonitor::networkDeviceInfoChanged, master, [master](const NetworkDeviceInfo &info) { - master->setHostAddress(info.address()); + if (!info.address().isNull()) + master->setHostAddress(info.address()); }); // Abort the setup if the info object is destroyed before we finish.