0
0
Fork 0
Codesys-MCP-SP21-plus/src/scripts/read_variable.py
Karstein Phobic Nyvold Kvistad ef259c8ee3 fix(online tools): auto-login + non-CONSTANT version GVL
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.
2026-04-26 19:54:13 +02:00

58 lines
2.1 KiB
Python

import sys, scriptengine as script_engine, os, traceback
VARIABLE_PATH = "{VARIABLE_PATH}"
try:
print("DEBUG: read_variable script: Variable='%s', Project='%s'" % (VARIABLE_PATH, PROJECT_FILE_PATH))
primary_project = ensure_project_open(PROJECT_FILE_PATH)
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")()
# Auto-login -- idempotent in persistent mode, required in headless.
ensure_logged_in(online_app)
# Read the variable value
value = None
var_type = "unknown"
if hasattr(online_app, 'read_value'):
try:
result = online_app.read_value(VARIABLE_PATH)
if result is not None:
if hasattr(result, 'value'):
value = str(result.value)
if hasattr(result, 'type'):
var_type = str(result.type)
else:
value = str(result)
print("DEBUG: read_value returned: %s" % value)
except Exception as e:
print("DEBUG: read_value failed: %s" % e)
raise
elif hasattr(online_app, 'read'):
try:
result = online_app.read(VARIABLE_PATH)
if result is not None:
value = str(result)
print("DEBUG: read returned: %s" % value)
except Exception as e:
print("DEBUG: read failed: %s" % e)
raise
else:
raise TypeError("Online application does not support read_value() or read().")
print("Variable: %s" % VARIABLE_PATH)
print("Value: %s" % value)
print("Type: %s" % var_type)
print("Application: %s" % app_name)
print("SCRIPT_SUCCESS: Variable read successfully.")
sys.exit(0)
except Exception as e:
detailed_error = traceback.format_exc()
error_message = "Error reading 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)