fix(write_variable): use SP22 prepare-then-write API as primary path
After 010811b's diagnostic dump revealed the actual online_app surface on
SP22 Patch 1:
['Dispose', 'application', 'application_state', 'create_boot_application',
'force_prepared_values', 'get_forced_expressions', 'get_online_device',
'get_prepared_expressions', 'get_prepared_value', 'is_logged_in', 'login',
'logout', 'operation_state', 'read_value', 'read_values', 'reset',
'set_prepared_value', 'set_unforce_value', 'source_download', 'start',
'stop', 'timeout', 'unforce_all_values', 'write_prepared_values']
There is no direct write_value / write / set_value method. The supported
pattern is two-step:
online_app.set_prepared_value(path, value) # stage
online_app.write_prepared_values() # commit
Asymmetric to read_value() (which is direct), but it's what the SP21+/SP22
scriptengine surface exposes.
This commit:
- Makes the prepare-then-write path the primary code path.
- Keeps the direct write_value / set_value / write / set fallbacks for
older SPs that still expose them (probe order: prepare-first, then
direct).
- Falls back to dumping dir(online_app) on total failure, same diagnostic
pattern that revealed this API in the first place.
Note for future reference: 'force_prepared_values()' is the alternate
commit method when you want to FORCE a value (override what the program
will write next cycle), versus 'write_prepared_values()' which is a normal
one-shot write.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
010811b342
commit
64906c4ab5
1 changed files with 26 additions and 34 deletions
|
|
@ -13,50 +13,42 @@ try:
|
|||
online_app, target_app = ensure_online_connection(primary_project)
|
||||
app_name = getattr(target_app, 'get_name', lambda: "Unknown")()
|
||||
|
||||
# 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 = []
|
||||
|
||||
# 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)]))
|
||||
|
||||
# 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
|
||||
)
|
||||
|
||||
# SP21+/SP22 uses a two-step prepare-then-write pattern:
|
||||
# 1) set_prepared_value(name, value) -- stage the value
|
||||
# 2) write_prepared_values() -- commit the staged writes
|
||||
# Older SPs (and some sandboxed configurations) expose direct
|
||||
# write_value(name, value) / write(name, value). Try the modern path
|
||||
# first, fall back to direct.
|
||||
written = False
|
||||
last_err = None
|
||||
for desc, method_name, args_list in candidates:
|
||||
|
||||
if hasattr(online_app, 'set_prepared_value') and hasattr(online_app, 'write_prepared_values'):
|
||||
try:
|
||||
getattr(online_app, method_name)(*args_list)
|
||||
print("DEBUG: %s OK" % desc)
|
||||
online_app.set_prepared_value(VARIABLE_PATH, VARIABLE_VALUE)
|
||||
online_app.write_prepared_values()
|
||||
print("DEBUG: set_prepared_value + write_prepared_values OK")
|
||||
written = True
|
||||
break
|
||||
except Exception as e:
|
||||
last_err = e
|
||||
print("DEBUG: %s failed: %s: %s" % (desc, type(e).__name__, e))
|
||||
print("DEBUG: set_prepared_value + write_prepared_values failed: %s: %s" % (type(e).__name__, e))
|
||||
|
||||
if not written:
|
||||
for method_name in ('write_value', 'set_value', 'write', 'set'):
|
||||
if not hasattr(online_app, method_name):
|
||||
continue
|
||||
try:
|
||||
getattr(online_app, method_name)(VARIABLE_PATH, VARIABLE_VALUE)
|
||||
print("DEBUG: %s(name, value) OK" % method_name)
|
||||
written = True
|
||||
break
|
||||
except Exception as e:
|
||||
last_err = e
|
||||
print("DEBUG: %s(name, value) failed: %s: %s" % (method_name, 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"
|
||||
"Could not write variable. Last error: %s\n"
|
||||
"Available attributes on online_app: %s" % (last_err, attrs)
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue