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 <noreply@anthropic.com>
End-to-end download via MCP was failing for three reasons; all three
fixed here so the agent can drive the whole flow without manual IDE
clicks.
1. verify_device_reachable did a live gateway.perform_network_scan()
on every call. That freezes the CODESYS UI thread for the full
scan duration, so the user couldn't interact with the IDE during
pre-flight (and a 60s tool timeout was easy to hit). Now reads
gateway.get_cached_network_scan_result() first (instant, no UI
block), only falls back to a live scan when no cache exists.
Adds scan_source ("cache" | "live") to the JSON so callers know
which path was taken.
2. rebind_device_to_scan short-circuited with "already-bound" when
the resolved address matched the cached one. But the IDE's
Select-Device + OK flow re-applies set_gateway_and_address even
when the address didn't change -- that's what refreshes the
device's scanned_* properties and re-establishes a session.
Skipping it left the binding stale even after a successful
rebind call. Always apply now.
3. ensure_online_connection created an online_application but never
opened a device session. login() then waited on (or popped a
dialog for) a session nobody had opened -- which is what the
user was doing manually as "right-click device -> Connect".
Adds _ensure_device_connected() helper that locates the project's
PLC device, creates a ScriptOnlineDevice, and calls connect() if
not already connected. Idempotent; best-effort (logs and falls
through on failure so login() can still try its own path).
The scan_network_devices, verify_device_reachable, and
rebind_device_to_scan templates used plain "..." strings for
PROJECT_FILE_PATH. With a UNC path like \\files\... the rendered Python
source had "\\files\..." which Python's source parser collapses to
"\files\..." (one backslash). ensure_project_open then saw the mangled
path as different from the (correctly-opened) primary, CLOSED the
primary project, and failed to reopen the corrupted path.
Other scripts use r"..." to bypass source-level escaping. Applying the
same to the three new ones.
Also bumps to 0.9.5.
Stale device addresses (cached in the project's Communication Settings)
are the #1 cause of download_to_device hanging silently: the gateway is
up, but the PLC has moved to a new router address after a reboot/DHCP
event, so login() waits forever on a UI dialog the agent can't see.
Adds three new tools:
- scan_network_devices: drives gateway.perform_network_scan() and emits
the list of discovered targets (device_name, type_name, vendor_name,
address, device_id). Supports useCache=true to read the gateway's
last cached scan result instead.
- verify_device_reachable: scans the gateway and reports whether the
project's cached device address matches a live target. Returns
reachable=true/false plus the candidate list.
- rebind_device_to_scan_result: matches a scan result (by name, by
device_id, by forced address, or single-candidate) and calls
device.set_gateway_and_address() + saves the project so the new
binding persists across CODESYS restarts.
And wires download_to_device to run verify_device_reachable BEFORE
login(). If unreachable, the tool fails fast with a clear hint to call
rebind_device_to_scan_result, instead of dropping into the IDE dialog.
Pass skipReachabilityCheck=true to force-bypass if the gateway/cache
lookup itself is broken on a given SP.
Helper find_target_device.py is shared across the three new scripts.