0
0
Fork 0

fix(read_running_version_online): document the real root cause + fix

Three changes after deeper investigation against PLATEA Win V3:

1. The 'symbol' := 'read' attribute experiment didn't help. Reverted
   bump_project_version's GVL template to plain VAR_GLOBAL +
   qualified_only (matches what shipped originally, minus CONSTANT).
   Comment updated to explain both why CONSTANT is wrong (compile-time
   inlining) and why the symbol attribute alone wasn't enough (it
   requires a Symbol Configuration object to do anything).

2. Real root cause for read_running_version_online's 'Invalid expression':
   CODESYS strips unreferenced GVLs from the online symbol table at
   compile time, regardless of attribute pragmas. The version anchor by
   definition has no IEC code reading it, so the optimizer drops it.
   GVL_Test.bRun reads fine despite no references because GVL_Test has
   OTHER referenced variables; entire-GVL retention seems to be the
   stripping unit, not per-variable.
   Verified end-to-end: adding 'sVersionTag := _MCP_PROJECT_VERSION
   .sVersion;' in PLC_PRG made the read return '1.4.1.0' on PLATEA.

3. Updated the read_running_version_online error message to surface
   BOTH the (now-rare) CONSTANT case AND the (common) unreferenced-GVL
   case, with the exact 2-line code snippet a user needs to paste into
   their main program. The bump tool intentionally does NOT auto-inject
   this -- modifying user code on every release was deemed too invasive.
   Documented the requirement in TEST_OVERVIEW.md alongside the v5
   sweep notes.

37/37 tests still green. v1.4.2.0 of MCPTest2 carries the working
PLC_PRG reference as the canonical demonstration.
This commit is contained in:
Karstein Phobic Nyvold Kvistad 2026-04-26 20:13:25 +02:00
parent ef259c8ee3
commit 04b46fb49a
3 changed files with 43 additions and 20 deletions

View file

@ -27,12 +27,18 @@ VALID_LEVELS = ('major', 'minor', 'revision', 'build')
# tool can pull it via online connect + read_variable. Kept as
# qualified_only so it can't accidentally shadow a same-named local.
VERSION_GVL_NAME = '_MCP_PROJECT_VERSION'
# NOT VAR_GLOBAL CONSTANT: CODESYS inlines CONSTANT scalars at compile time,
# which strips them from the online symbol table -- read_running_version_online
# would then fail with 'Invalid expression' on every project bumped via this
# tool. Plain VAR_GLOBAL keeps the symbol live so the online tool can read
# the running version. The string is still effectively read-only at runtime
# (only bump_project_version updates it via textual_declaration.replace).
# Plain VAR_GLOBAL (NOT CONSTANT). CODESYS inlines CONSTANT scalars at
# compile time and strips them from the online symbol table, which would
# break read_running_version_online with 'Invalid expression'. Plain
# VAR_GLOBAL keeps the symbol live without needing extra attributes -- a
# verified-working test case in MCPTest2 (GVL_Test.bRun) reads fine over
# the online protocol despite zero IEC references, so a Symbol
# Configuration / 'symbol' attribute is NOT required for the runtime to
# expose unreferenced globals. The string is still effectively read-only
# at runtime since only bump_project_version updates it via
# textual_declaration.replace.
# qualified_only attribute kept so callers must use the full qualified
# name in IEC code (avoids accidental shadowing of a same-named local).
VERSION_GVL_DECLARATION_TEMPLATE = (
"{attribute 'qualified_only'}\n"
"VAR_GLOBAL\n"

View file

@ -40,20 +40,37 @@ try:
msg = str(e)
msg_l = msg.lower()
if 'invalid expression' in msg_l:
# Most common cause: the GVL is declared VAR_GLOBAL CONSTANT,
# which CODESYS inlines at compile time -- the symbol never
# makes it into the online table. This is a known footgun
# because older bump_project_version emitted CONSTANT GVLs.
# Two known causes lead here. Surface both with concrete
# next steps; the user usually only hits ONE of them.
raise RuntimeError(
"Online evaluator returned 'Invalid expression' for '%s'. "
"Most likely cause: the _MCP_PROJECT_VERSION GVL was created "
"with VAR_GLOBAL CONSTANT, which CODESYS inlines at compile "
"time so the symbol is not in the online symbol table. "
"Fix: edit _MCP_PROJECT_VERSION's declaration to drop "
"CONSTANT (just VAR_GLOBAL), then bump_project_version + "
"download_to_device. (newer bump_project_version emits "
"non-CONSTANT GVLs by default, so future-bumped projects "
"are unaffected.) Underlying error: %s" % (VARIABLE_PATH, e)
"Online evaluator returned 'Invalid expression' for '%s'.\n"
"\n"
"TWO POSSIBLE CAUSES:\n"
"\n"
"(1) Old projects: the _MCP_PROJECT_VERSION GVL was created\n"
" with VAR_GLOBAL CONSTANT. CODESYS inlines CONSTANT\n"
" scalars at compile time so the symbol never reaches\n"
" the online symbol table. Newer bump_project_version\n"
" emits plain VAR_GLOBAL; existing projects auto-migrate\n"
" on the next bump (the existing-GVL branch overwrites\n"
" the declaration with the new template).\n"
" Fix: run bump_project_version once + download_to_device.\n"
"\n"
"(2) Common case: CODESYS strips unreferenced GVLs from the\n"
" online symbol table even if they're plain VAR_GLOBAL.\n"
" By definition no IEC code references the version anchor,\n"
" so the optimizer drops it. The bump tool does NOT\n"
" auto-inject a reference (too invasive on user code).\n"
" Fix: add a one-liner to your main PROGRAM (typically\n"
" PLC_PRG). Declare a STRING var like\n"
" sVersionTag : STRING;\n"
" and assign it at the top of the implementation:\n"
" sVersionTag := _MCP_PROJECT_VERSION.sVersion;\n"
" Then download_to_device. The reference forces the\n"
" symbol into the online table; this tool will then\n"
" read it successfully.\n"
"\n"
"Underlying error: %s" % (VARIABLE_PATH, e)
)
if 'not found' in msg_l or 'unknown' in msg_l or 'symbol' in msg_l:
raise RuntimeError(

View file

@ -120,7 +120,7 @@ These all require a running PLC and a configured device gateway. Persistent timi
| `write_variable` | ✅ verified end-to-end (v5: wrote 200 → read 204 4s later, 1 Hz tick proves write took) | `set_prepared_value` + `write_prepared_values` (SP21+ path), falls back to `write_value` / `set_value` / `write` / `set` for older SPs | 100500 ms | now auto-logs-in (v5 fix) |
| `download_to_device` | ✅ verified end-to-end (v5: pushed v1.3.4.0 to PLATEA) | Pushes the new boot application after a code change. Heavy. Has its own login probe (independent from auto-login helper). | 560 s (project size dependent) | runs end-to-end |
| `start_stop_application` | ✅ verified end-to-end (v5: stop → `stop` state, start → `run` state) | `online_app.start()` / `.stop()` | 200500 ms | now auto-logs-in (v5 fix) |
| `read_running_version_online` | **broken-by-design pre-v5; fixed in this sweep** | Reads `_MCP_PROJECT_VERSION.sVersion` from the running PLC. **Old `bump_project_version` emitted the GVL as `VAR_GLOBAL CONSTANT`, which CODESYS inlines at compile time, so the symbol never reaches the online evaluator -- read returns `Invalid expression`.** Fixed in v5 by dropping `CONSTANT` from `VERSION_GVL_DECLARATION_TEMPLATE`. Existing projects auto-migrate on next bump (the existing-GVL branch overwrites `textual_declaration` with the new template). Script also detects the failure mode and emits a precise actionable error. | 100500 ms | now auto-logs-in (v5 fix) |
| `read_running_version_online` | ✅ verified end-to-end (v5: returns `1.4.1.0` for MCPTest2 v1.4.1.0); ⚠ requires IEC code reference -- see notes | Reads `_MCP_PROJECT_VERSION.sVersion` from the running PLC. **CODESYS strips unreferenced GVLs from the online symbol table at compile time -- adding `CONSTANT` makes it strictly worse (CONSTANT scalars are inlined and never reach the symbol DB at all), but even plain VAR_GLOBAL is dropped if no IEC code references the variable.** Fixed CONSTANT in v5 (dropped from `VERSION_GVL_DECLARATION_TEMPLATE` in `bump_project_version.py`); existing projects auto-migrate on next bump. **Caveat:** for `read_running_version_online` to actually return a value, *some IEC code must read `_MCP_PROJECT_VERSION.sVersion`*. Recommended one-time setup: add a string variable to PLC_PRG and assign `sVersionTag := _MCP_PROJECT_VERSION.sVersion;` once at the top of the implementation. The bump tool does NOT auto-inject this (it would be too invasive on user code). The script's "Invalid expression" error message points users at this requirement. | 100500 ms | now auto-logs-in (v5 fix) |
#### Headless mode + device tools (v5 deep-dive)