fix(write_variable): probe write methods + diagnostic dump on miss
Mirror of the connect_to_device probe pattern (e862846). The fork's
prior write_variable.py hard-coded write_value() then write() and
errored if neither existed; on SP22 Patch 1 neither is exposed on
online_app, even though the read counterpart (read_value()) works.
Now tries six method names in priority order:
- Single-write: write_value, set_value, write, set
- Batch-write: write_values, set_values (passes [(name, value)])
On total miss, dumps sorted dir(online_app) so the next debug session
sees exactly what the live online application object exposes -- the
same diagnostic technique that found 'librarymanager' in the earlier
install_library_file probe.
read_variable already works (uses read_value()) so the asymmetry is
specifically on the write side. Verifying by re-running write_variable
after the MCP restart will reveal which method actually exists, and
we can pin it explicitly in a follow-up if useful.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
eee8ce2d1e
commit
010811b342
1 changed files with 50 additions and 20 deletions
|
|
@ -4,32 +4,61 @@ VARIABLE_PATH = "{VARIABLE_PATH}"
|
||||||
VARIABLE_VALUE = "{VARIABLE_VALUE}"
|
VARIABLE_VALUE = "{VARIABLE_VALUE}"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
print("DEBUG: write_variable script: Variable='%s', Value='%s', Project='%s'" % (VARIABLE_PATH, VARIABLE_VALUE, PROJECT_FILE_PATH))
|
print("DEBUG: write_variable script: Variable='%s', Value='%s', Project='%s'" % (
|
||||||
|
VARIABLE_PATH, VARIABLE_VALUE, PROJECT_FILE_PATH))
|
||||||
primary_project = ensure_project_open(PROJECT_FILE_PATH)
|
primary_project = ensure_project_open(PROJECT_FILE_PATH)
|
||||||
if not VARIABLE_PATH: raise ValueError("Variable path empty.")
|
if not VARIABLE_PATH:
|
||||||
|
raise ValueError("Variable path empty.")
|
||||||
|
|
||||||
online_app, target_app = ensure_online_connection(primary_project)
|
online_app, target_app = ensure_online_connection(primary_project)
|
||||||
app_name = getattr(target_app, 'get_name', lambda: "Unknown")()
|
app_name = getattr(target_app, 'get_name', lambda: "Unknown")()
|
||||||
|
|
||||||
# Write the variable value
|
# The write counterpart of read_value() has shifted across CODESYS SPs.
|
||||||
if hasattr(online_app, 'write_value'):
|
# Some expose 'write_value(name, value)', some 'set_value(...)',
|
||||||
try:
|
# and some only the batch form 'write_values([(name, value), ...])'.
|
||||||
online_app.write_value(VARIABLE_PATH, VARIABLE_VALUE)
|
# Probe each available method, then fall back to dumping the available
|
||||||
print("DEBUG: write_value succeeded.")
|
# methods on online_app so future debug sessions know what's exposed.
|
||||||
except Exception as e:
|
candidates = []
|
||||||
print("DEBUG: write_value failed: %s" % e)
|
|
||||||
raise
|
|
||||||
|
|
||||||
elif hasattr(online_app, 'write'):
|
# Single-write methods, (name, value) style
|
||||||
try:
|
for method_name in ('write_value', 'set_value', 'write', 'set'):
|
||||||
online_app.write(VARIABLE_PATH, VARIABLE_VALUE)
|
if hasattr(online_app, method_name):
|
||||||
print("DEBUG: write succeeded.")
|
candidates.append((method_name + '(name, value)', method_name, [(VARIABLE_PATH, VARIABLE_VALUE)]))
|
||||||
except Exception as e:
|
|
||||||
print("DEBUG: write failed: %s" % e)
|
|
||||||
raise
|
|
||||||
|
|
||||||
else:
|
# Batch-write methods, [(name, value), ...] style
|
||||||
raise TypeError("Online application does not support write_value() or write().")
|
for method_name in ('write_values', 'set_values'):
|
||||||
|
if hasattr(online_app, method_name):
|
||||||
|
candidates.append((method_name + '([(name, value)])', method_name, [[(VARIABLE_PATH, VARIABLE_VALUE)]]))
|
||||||
|
|
||||||
|
if not candidates:
|
||||||
|
# No known method exposed. Dump diagnostic.
|
||||||
|
attrs = sorted([a for a in dir(online_app) if not a.startswith('_')])
|
||||||
|
raise TypeError(
|
||||||
|
"Online application exposes no known write method "
|
||||||
|
"(tried write_value/set_value/write/set/write_values/set_values).\n"
|
||||||
|
"Available attributes: %s" % attrs
|
||||||
|
)
|
||||||
|
|
||||||
|
written = False
|
||||||
|
last_err = None
|
||||||
|
for desc, method_name, args_list in candidates:
|
||||||
|
try:
|
||||||
|
getattr(online_app, method_name)(*args_list)
|
||||||
|
print("DEBUG: %s OK" % desc)
|
||||||
|
written = True
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
last_err = e
|
||||||
|
print("DEBUG: %s failed: %s: %s" % (desc, type(e).__name__, e))
|
||||||
|
|
||||||
|
if not written:
|
||||||
|
# All known method names were exposed but every call failed. Dump
|
||||||
|
# diagnostic + last error so we can iterate.
|
||||||
|
attrs = sorted([a for a in dir(online_app) if not a.startswith('_')])
|
||||||
|
raise RuntimeError(
|
||||||
|
"All known write methods were rejected. Last error: %s\n"
|
||||||
|
"Available attributes on online_app: %s" % (last_err, attrs)
|
||||||
|
)
|
||||||
|
|
||||||
print("Variable: %s" % VARIABLE_PATH)
|
print("Variable: %s" % VARIABLE_PATH)
|
||||||
print("Value Written: %s" % VARIABLE_VALUE)
|
print("Value Written: %s" % VARIABLE_VALUE)
|
||||||
|
|
@ -38,7 +67,8 @@ try:
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
detailed_error = traceback.format_exc()
|
detailed_error = traceback.format_exc()
|
||||||
error_message = "Error writing variable '%s' in project %s: %s\n%s" % (VARIABLE_PATH, PROJECT_FILE_PATH, e, detailed_error)
|
error_message = "Error writing variable '%s' in project %s: %s\n%s" % (
|
||||||
|
VARIABLE_PATH, PROJECT_FILE_PATH, e, detailed_error)
|
||||||
print(error_message)
|
print(error_message)
|
||||||
print("SCRIPT_ERROR: %s" % error_message)
|
print("SCRIPT_ERROR: %s" % error_message)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue