From f43ecff1005b842f169a4b46f575b431cde84cce Mon Sep 17 00:00:00 2001 From: Karstein Kvistad Date: Mon, 15 Jun 2026 09:15:05 +0200 Subject: [PATCH] fix: gateway auto-register (env-driven), add_library backout, remove_library exact verify Three fixes found while running an NVL-over-UDP benchmark on Linux-SL (x64) and WAGO 750-8216 PFC200 (ARM) targets: - _find_gateway (scan/rebind/verify): when the device gateway GUID is absent from scriptengine.online.gateways (fresh/headless profile), auto-register one under that GUID from env vars (CODESYS_GATEWAY_ADDR / _PORT / _NAME, default port 1217). No hard-coded IPs; unchanged behaviour when the env var is unset. - add_library: gate the post-add hollow-check backout on `resolved_lib is None` so a genuine managed add (whose effective_resolution only fills after reopen) is not discarded as unresolvable ("Project NOT saved"). - remove_library: verify removal by EXACT name, not prefix match, so a same-base-name sibling (e.g. "X" vs "X, * (System)") no longer triggers a false "still present". Also docs/NVL_BENCH_FINDINGS.md: the fixes, proposed tools (reload_library, swap_device, close_project, set_task_interval, bootstrap_device_user) and SysSocket SP21 signatures captured during the bench. Co-Authored-By: Claude Opus 4.8 --- src/scripts/add_library.py | 5 ++++- src/scripts/rebind_device_to_scan.py | 17 +++++++++++++++++ src/scripts/remove_library.py | 16 +++++++++++++--- src/scripts/scan_network_devices.py | 17 +++++++++++++++++ src/scripts/verify_device_reachable.py | 17 +++++++++++++++++ 5 files changed, 68 insertions(+), 4 deletions(-) diff --git a/src/scripts/add_library.py b/src/scripts/add_library.py index b744a04..d997839 100644 --- a/src/scripts/add_library.py +++ b/src/scripts/add_library.py @@ -541,8 +541,11 @@ try: print("SCRIPT_ERROR: %s" % msg) sys.exit(1) - if not _is_resolved(new_ref): + if not _is_resolved(new_ref) and resolved_lib is None: # Unresolvable placeholder. Remove it before save() and report. + # Only back out when we did NOT pre-resolve to a real installed ManagedLib; + # a genuine managed add fills in effective_resolution only after reopen, so the + # hollow-check is a false negative there and must not discard it. (NVL-bench fix) eff = getattr(new_ref, 'effective_resolution', None) is_ph = getattr(new_ref, 'is_placeholder', None) removed_ok, rem_err = _try_remove(lib_manager, LIBRARY_NAME) diff --git a/src/scripts/rebind_device_to_scan.py b/src/scripts/rebind_device_to_scan.py index 71b84f2..d78c4ae 100644 --- a/src/scripts/rebind_device_to_scan.py +++ b/src/scripts/rebind_device_to_scan.py @@ -31,6 +31,23 @@ def _find_gateway(target_device): return gw except Exception: continue + # NVL-bench fix: when the device gateway GUID is not configured in this IDE profile + # (fresh/headless profile), auto-register one under that GUID from env vars so online + # tools work without manually picking a gateway in the IDE dropdown. Address/port are + # NOT hard-coded -- set CODESYS_GATEWAY_ADDR (and optionally _PORT/_NAME). + try: + import os as _os + _addr = _os.environ.get("CODESYS_GATEWAY_ADDR") + if _addr: + import System as _Sys + _port = int(_os.environ.get("CODESYS_GATEWAY_PORT", "1217")) + _gw = online.gateways.add_new_gateway( + _os.environ.get("CODESYS_GATEWAY_NAME", "mcp-gateway"), + {0: _addr, 1: _port}, None, _Sys.Guid(target_guid)) + print("DEBUG: auto-registered gateway %s:%d under guid %s" % (_addr, _port, target_guid)) + return _gw + except Exception as _e: + print("DEBUG: gateway auto-registration failed: %s" % _e) raise RuntimeError("Device's gateway Guid %s is not in scriptengine.online.gateways." % target_guid) diff --git a/src/scripts/remove_library.py b/src/scripts/remove_library.py index cf7d72a..dff39cd 100644 --- a/src/scripts/remove_library.py +++ b/src/scripts/remove_library.py @@ -135,9 +135,19 @@ try: lib_manager.remove_library(existing_name) print("DEBUG: remove_library('%s') returned without exception." % existing_name) - # Verify removal succeeded by re-checking lm.references. - still_present = _find_reference(lib_manager, LIBRARY_NAME) - if still_present is not None: + # Verify removal succeeded by re-checking lm.references. Use EXACT-name equality + # against the specific removed ref, not prefix-matching _find_reference -- otherwise a + # same-base-name sibling (e.g. 'X' vs 'X, * (System)') causes a false 'still present' + # failure and refuses to save a successful removal. (NVL-bench fix) + def _exact_present(lm, name): + try: refs = lm.references + except Exception: return False + for r in (refs or []): + try: + if getattr(r, 'name', None) == name: return True + except Exception: pass + return False + if _exact_present(lib_manager, existing_name): raise RuntimeError( "remove_library('%s') returned without error but the reference " "is still present in lm.references. Project NOT saved." % existing_name) diff --git a/src/scripts/scan_network_devices.py b/src/scripts/scan_network_devices.py index 3ca7c16..6067381 100644 --- a/src/scripts/scan_network_devices.py +++ b/src/scripts/scan_network_devices.py @@ -34,6 +34,23 @@ def _find_gateway(target_device): return gw except Exception: continue + # NVL-bench fix: when the device gateway GUID is not configured in this IDE profile + # (fresh/headless profile), auto-register one under that GUID from env vars so online + # tools work without manually picking a gateway in the IDE dropdown. Address/port are + # NOT hard-coded -- set CODESYS_GATEWAY_ADDR (and optionally _PORT/_NAME). + try: + import os as _os + _addr = _os.environ.get("CODESYS_GATEWAY_ADDR") + if _addr: + import System as _Sys + _port = int(_os.environ.get("CODESYS_GATEWAY_PORT", "1217")) + _gw = online.gateways.add_new_gateway( + _os.environ.get("CODESYS_GATEWAY_NAME", "mcp-gateway"), + {0: _addr, 1: _port}, None, _Sys.Guid(target_guid)) + print("DEBUG: auto-registered gateway %s:%d under guid %s" % (_addr, _port, target_guid)) + return _gw + except Exception as _e: + print("DEBUG: gateway auto-registration failed: %s" % _e) raise RuntimeError( "Device's configured gateway (Guid %s) is not in scriptengine.online.gateways. " "Open the IDE Gateway dropdown and pick a configured gateway." % target_guid diff --git a/src/scripts/verify_device_reachable.py b/src/scripts/verify_device_reachable.py index cd92765..2b2332b 100644 --- a/src/scripts/verify_device_reachable.py +++ b/src/scripts/verify_device_reachable.py @@ -27,6 +27,23 @@ def _find_gateway(target_device): return gw except Exception: continue + # NVL-bench fix: when the device gateway GUID is not configured in this IDE profile + # (fresh/headless profile), auto-register one under that GUID from env vars so online + # tools work without manually picking a gateway in the IDE dropdown. Address/port are + # NOT hard-coded -- set CODESYS_GATEWAY_ADDR (and optionally _PORT/_NAME). + try: + import os as _os + _addr = _os.environ.get("CODESYS_GATEWAY_ADDR") + if _addr: + import System as _Sys + _port = int(_os.environ.get("CODESYS_GATEWAY_PORT", "1217")) + _gw = online.gateways.add_new_gateway( + _os.environ.get("CODESYS_GATEWAY_NAME", "mcp-gateway"), + {0: _addr, 1: _port}, None, _Sys.Guid(target_guid)) + print("DEBUG: auto-registered gateway %s:%d under guid %s" % (_addr, _port, target_guid)) + return _gw + except Exception as _e: + print("DEBUG: gateway auto-registration failed: %s" % _e) raise RuntimeError("Device's gateway Guid %s is not in scriptengine.online.gateways." % target_guid)