fix(connect_to_device): probe OnlineChangeOption + try login() call shapes
Defensively iterates plausible signatures for IOnlineApplication.login()
across SP versions. Prior version hard-coded
'login(OnlineChangeOption.TryOnlineChange)' which fails on SP21+/SP22:
- 'TryOnlineChange' enum member was removed
- login() now requires (OnlineChangeOption, bool) -- two positional args
Concrete failure observed today on SP22 Patch 1:
- First fallback raised: 'type' object has no attribute 'TryOnlineChange'
- Second fallback raised: login() takes exactly 2 arguments (0 given)
...with the runtime actually reachable on port 11740. So a real call
was waiting for the right arguments.
New approach:
1) Discover OnlineChangeOption members at runtime via dir(); print them
so future debug sessions see exactly what's exposed on this CODESYS.
2) Try (enum, False) / (enum, True) / (enum,) for each candidate enum
value, prioritising 'Try'-ish names then 'WithDownload' / others.
3) Fall back to bool-only and no-arg for very old SPs.
4) Log every attempt (DEBUG line) so a failure trace shows the full
matrix that was tried.
Companion fixes for download_to_device, write_variable, etc. coming as
separate commits if their root cause is the same login API drift.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9f44a603d8
commit
e862846164
1 changed files with 57 additions and 15 deletions
|
|
@ -7,24 +7,66 @@ try:
|
|||
online_app, target_app = ensure_online_connection(primary_project)
|
||||
app_name = getattr(target_app, 'get_name', lambda: "Unknown")()
|
||||
|
||||
# Login to the device
|
||||
# Login to the device. The login() signature shifted across SPs:
|
||||
# - Older: login() with no args, or login(OnlineChangeOption.TryOnlineChange)
|
||||
# - SP21+/SP22: login(OnlineChangeOption, bool) -- two required positional
|
||||
# args, with several enum members renamed (TryOnlineChange removed).
|
||||
# Probe what's available, then try a sequence of plausible call shapes.
|
||||
print("DEBUG: Calling login() on online application...")
|
||||
|
||||
if hasattr(online_app, 'login'):
|
||||
# Try with OnlineChangeOption if available
|
||||
if hasattr(script_engine, 'OnlineChangeOption'):
|
||||
try:
|
||||
online_app.login(script_engine.OnlineChangeOption.TryOnlineChange)
|
||||
print("DEBUG: Logged in with TryOnlineChange option.")
|
||||
except Exception as e:
|
||||
print("DEBUG: Login with OnlineChangeOption failed, trying plain login: %s" % e)
|
||||
online_app.login()
|
||||
else:
|
||||
online_app.login()
|
||||
print("DEBUG: Login successful.")
|
||||
else:
|
||||
if not hasattr(online_app, 'login'):
|
||||
raise TypeError("Online application does not support login().")
|
||||
|
||||
# Discover OnlineChangeOption members defensively. Different SPs expose
|
||||
# different names. Build candidate enum values in priority order.
|
||||
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)
|
||||
# Priority order: prefer "try"-ish (no-download), then "download" variants
|
||||
for preferred in ('Try', 'TryOnlineChange', 'OnlineChangeOnly',
|
||||
'WithDownload', 'ForceDownload', 'None_', 'None'):
|
||||
if preferred in oc_members:
|
||||
try:
|
||||
enum_candidates.append((preferred, getattr(oc, preferred)))
|
||||
except Exception:
|
||||
pass
|
||||
# Append all remaining members as fallbacks
|
||||
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
|
||||
|
||||
# Build call-shape candidates for login(): a list of (description, args-tuple).
|
||||
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,)))
|
||||
# Also try plain bools and no-arg as fall-backs (for very old SPs)
|
||||
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 only the short error to keep log readable
|
||||
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)
|
||||
print("DEBUG: Login successful.")
|
||||
|
||||
# Check connection state
|
||||
state = "connected"
|
||||
if hasattr(online_app, 'application_state'):
|
||||
|
|
|
|||
Loading…
Reference in a new issue