Two related v5-sweep fixes for the online/runtime tool family:
1. Auto-login helper for headless mode
In headless mode each MCP call spawns a fresh CODESYS --noUI process,
so the login state established by connect_to_device dies before the
next call. Pre-fix, only connect_to_device and download_to_device did
their own login(); the other four (start_stop_application,
read_variable, write_variable, read_running_version_online) silently
failed in headless with 'Application not logged in.' (start/stop) or
'Invalid expression' (read/write). They worked in persistent mode
only because the login carried across calls.
Added ensure_logged_in(online_app, login_wait_seconds=30) to
ensure_online_connection.py. Idempotent: short-circuits via
online_app.is_logged_in (persistent mode is a no-op, no extra login
roundtrip). When not logged in, runs the same enum-probe + call-shape
probe + STABLE_STATES settle-wait pattern as connect_to_device.py.
Added to start_stop_application.py, read_variable.py,
write_variable.py, read_running_version_online.py.
2. _MCP_PROJECT_VERSION GVL emitted as plain VAR_GLOBAL, not CONSTANT
CODESYS inlines VAR_GLOBAL CONSTANT scalars at compile time and
strips them from the online symbol table. The whole point of
_MCP_PROJECT_VERSION.sVersion is to be readable live from the
running PLC, so CONSTANT was the wrong storage class.
read_running_version_online failed against EVERY project bumped via
the old template -- 'Invalid expression' on the runtime read.
Dropped CONSTANT from VERSION_GVL_DECLARATION_TEMPLATE in
bump_project_version.py. Existing projects auto-migrate on the next
bump because maintain_version_gvl()'s existing-GVL branch overwrites
textual_declaration with the (now non-CONSTANT) template. The string
is still effectively read-only at runtime -- only the bump tool
updates it.
read_running_version_online.py also got a more precise error message
that explicitly fingerprints the 'Invalid expression' failure mode
and points at the CONSTANT root cause. Useful for any user landing
on a project that pre-dates this fix.
Verified end-to-end against local CODESYS Control Win V3 (PLATEA, port
11740) on MCPTest2 v1.3.4.0:
- connect_to_device, get_application_state, download_to_device,
start_stop_application (both directions), read_variable
(PLC_PRG.watchdog1 = 225 ticking), write_variable (200 -> 204 in 4s
proves write took), disconnect_from_device: all 7 PASS.
- read_running_version_online failure reproduced (CONSTANT inlined),
fix landed -- next bump on MCPTest2 will validate.
37/37 unit/integration tests green. TEST_OVERVIEW.md updated with the
v5 device sweep, with the headless-mode deep-dive, and with the
broken-by-design notes on read_running_version_online.
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>
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>