From 010811b342d9fdb6295e9e9d0001984e30c9d431 Mon Sep 17 00:00:00 2001 From: Karstein Phobic Nyvold Kvistad Date: Sat, 25 Apr 2026 18:32:57 +0200 Subject: [PATCH] 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) --- src/scripts/write_variable.py | 70 +++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 20 deletions(-) diff --git a/src/scripts/write_variable.py b/src/scripts/write_variable.py index 3fa852a..d77862a 100644 --- a/src/scripts/write_variable.py +++ b/src/scripts/write_variable.py @@ -4,32 +4,61 @@ VARIABLE_PATH = "{VARIABLE_PATH}" VARIABLE_VALUE = "{VARIABLE_VALUE}" 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) - 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) app_name = getattr(target_app, 'get_name', lambda: "Unknown")() - # Write the variable value - if hasattr(online_app, 'write_value'): - try: - online_app.write_value(VARIABLE_PATH, VARIABLE_VALUE) - print("DEBUG: write_value succeeded.") - except Exception as e: - print("DEBUG: write_value failed: %s" % e) - raise + # The write counterpart of read_value() has shifted across CODESYS SPs. + # Some expose 'write_value(name, value)', some 'set_value(...)', + # and some only the batch form 'write_values([(name, value), ...])'. + # Probe each available method, then fall back to dumping the available + # methods on online_app so future debug sessions know what's exposed. + candidates = [] - elif hasattr(online_app, 'write'): - try: - online_app.write(VARIABLE_PATH, VARIABLE_VALUE) - print("DEBUG: write succeeded.") - except Exception as e: - print("DEBUG: write failed: %s" % e) - raise + # Single-write methods, (name, value) style + for method_name in ('write_value', 'set_value', 'write', 'set'): + if hasattr(online_app, method_name): + candidates.append((method_name + '(name, value)', method_name, [(VARIABLE_PATH, VARIABLE_VALUE)])) - else: - raise TypeError("Online application does not support write_value() or write().") + # Batch-write methods, [(name, value), ...] style + 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("Value Written: %s" % VARIABLE_VALUE) @@ -38,7 +67,8 @@ try: sys.exit(0) except Exception as e: 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("SCRIPT_ERROR: %s" % error_message) sys.exit(1)