0
0
Fork 0
Codesys-MCP-SP21-plus/src/scripts/download_to_device.py
Karstein Phobic Nyvold Kvistad b3bf4a83d5 fix(download_to_device): port login() probe + loginWaitSeconds from connect
Same SP21+/SP22 login() drift as connect_to_device:
  - 'TryOnlineChange' is gone from OnlineChangeOption
  - login() now requires (OnlineChangeOption, bool) -- two positional
The fork hard-coded the old call shape; observed today on SP22 Patch 1
with the runtime up and the project compiled, every download_to_device
attempt failed with "login() takes exactly 2 arguments (0 given)".

Fix mirrors e862846 / eee8ce2:
  - Probe OnlineChangeOption members at runtime (priority order tuned
    for download: WithDownload / ForceDownload come first, since
    'download' implies a write).
  - Try (enum, False) / (enum, True) / (enum,) / bool / no-arg shapes.
  - Add LOGIN_WAIT_SECONDS post-login state-stabilisation poll for the
    credential dialog (default 60s, configurable 0-600).
  - Tool-side IPC timeout = waitSec + 120s headroom for the actual
    download work (download is heavier than connect, hence 120 vs 30).

The download itself is still done via the existing fall-through:
online_app.download() if exposed, else create_boot_application().
On SP22 the dir() shows source_download + create_boot_application (no
'download'), so the create_boot_application path is what runs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 18:40:26 +02:00

102 lines
4.1 KiB
Python

import sys, scriptengine as script_engine, os, traceback
LOGIN_WAIT_SECONDS = {LOGIN_WAIT_SECONDS}
try:
print("DEBUG: download_to_device script: Project='%s', LoginWaitSec=%d" % (
PROJECT_FILE_PATH, LOGIN_WAIT_SECONDS))
primary_project = ensure_project_open(PROJECT_FILE_PATH)
online_app, target_app = ensure_online_connection(primary_project)
app_name = getattr(target_app, 'get_name', lambda: "Unknown")()
# Login. Same SP-version drift as connect_to_device: TryOnlineChange
# is gone, login() now requires (OnlineChangeOption, bool) on SP21+.
# Use the same defensive probe + post-login wait pattern.
print("DEBUG: Logging in for download...")
if not hasattr(online_app, 'login'):
raise TypeError("Online application does not support login().")
enum_candidates = []
if hasattr(script_engine, 'OnlineChangeOption'):
oc = script_engine.OnlineChangeOption
oc_members = sorted([m for m in dir(oc) if not m.startswith('_')])
print("DEBUG: OnlineChangeOption members: %s" % oc_members)
# For download we prefer 'WithDownload' / 'ForceDownload' over 'Try'
for preferred in ('WithDownload', 'ForceDownload', 'Try',
'TryOnlineChange', 'OnlineChangeOnly', 'None_', 'None'):
if preferred in oc_members:
try:
enum_candidates.append((preferred, getattr(oc, preferred)))
except Exception:
pass
for m in oc_members:
if m not in [n for n, _ in enum_candidates]:
try:
enum_candidates.append((m, getattr(oc, m)))
except Exception:
pass
call_shapes = []
for nm, val in enum_candidates:
call_shapes.append(("login(%s, False)" % nm, (val, False)))
call_shapes.append(("login(%s, True)" % nm, (val, True)))
call_shapes.append(("login(%s)" % nm, (val,)))
call_shapes.append(("login(False)", (False,)))
call_shapes.append(("login(True)", (True,)))
call_shapes.append(("login()", ()))
last_err = None
logged_in = False
for desc, args in call_shapes:
try:
online_app.login(*args)
print("DEBUG: %s succeeded" % desc)
logged_in = True
break
except Exception as e:
last_err = e
print("DEBUG: %s failed: %s: %s" % (desc, type(e).__name__, e))
if not logged_in:
raise RuntimeError("All login() call shapes failed. Last error: %s" % last_err)
# Wait for state to stabilise (credential dialog handling)
print("DEBUG: login() returned. Waiting up to %d seconds for state to stabilise" % LOGIN_WAIT_SECONDS)
STABLE_STATES = ('run', 'stop', 'connected', 'halt', 'breakpoint')
for elapsed in range(LOGIN_WAIT_SECONDS):
state = "unknown"
if hasattr(online_app, 'application_state'):
try:
state = str(online_app.application_state)
except Exception:
pass
if state.lower() in STABLE_STATES:
print("DEBUG: state stabilised at '%s' after %ds" % (state, elapsed))
break
try:
script_engine.system.delay(1000)
except Exception:
pass
# Download
print("DEBUG: Calling download()...")
if hasattr(online_app, 'download'):
online_app.download()
print("DEBUG: Download complete.")
elif hasattr(online_app, 'create_boot_application'):
# Alternative: some versions use create_boot_application
online_app.create_boot_application()
print("DEBUG: Boot application created.")
else:
raise TypeError("Online application does not support download().")
print("Downloaded to device for application: %s" % app_name)
print("SCRIPT_SUCCESS: Application downloaded to device successfully.")
sys.exit(0)
except Exception as e:
detailed_error = traceback.format_exc()
error_message = "Error downloading to device for project %s: %s\n%s" % (PROJECT_FILE_PATH, e, detailed_error)
print(error_message)
print("SCRIPT_ERROR: %s" % error_message)
sys.exit(1)