Skip to content

OPC UA plugin: STRING/WSTRING variables are silently unsupported (always read as empty, writes always fail) #168

Description

@alkoval72

Environment

  • OpenPLC Editor: OpenPLC.Editor-4.2.11.AppImage (Ubuntu)
  • OpenPLC Runtime: ghcr.io/autonomy-logic/openplc-runtime:v4.1.10, arm64
  • Deployment: Docker container, Debian 13 host, ARM64 SBC
  • OPC UA server: built-in Runtime OPC UA plugin, path /openplc/opcua, port 4840

Summary

Any IEC STRING (or WSTRING) variable exposed via the OPC UA server always reads back as an empty string over OPC UA, regardless of its actual value inside the running PLC program, and any client write to such a variable silently fails to reach the program's memory. Non-string types (BOOL, INT family, REAL/LREAL, TIME family) are unaffected.

Root cause (found by reading the plugin source)

core/src/drivers/plugins/python/opcua/opcua_memory.py, _ctype_for():

def _ctype_for(datatype: str) -> Optional[Any]:
    ...
    if t in ("STRING", "WSTRING"):
        return None  # variable-length, not yet supported by debug surface

Both debug_read_value() and debug_write_value() in the same file short-circuit on ctype is None:

  • debug_read_value() returns None immediately, with the comment # STRING/WSTRING — not supported yet (Phase 4a in debug_dispatch.hpp explicitly stubs string reads).
  • debug_write_value() returns False immediately, without ever calling args.debug_write.

In synchronization.py, the read path silently substitutes a default value on None:

@staticmethod
def _get_default_value(datatype: str) -> Any:
    ...
    if dtype == "STRING":
        return ""
    return 0

...so every push to an OPC UA client for a STRING variable is unconditionally "", no matter what the program actually holds.

The write path logs a failure for every attempted write to a STRING variable:

ok = debug_write_value(self.args, addr[0], addr[1], datatype, plc_value)
if not ok:
    log_error(f"debug_write({addr[0]}, {addr[1]}) failed")

This matches the runtime log output exactly: repeated [ERROR]: debug_write(0, N) failed lines for the (arr, elem) addresses of any STRING variable being written from an OPC UA client (or otherwise touched by the plugin's write path), recurring for as long as the variable keeps getting written.

Steps to reproduce

  1. In an OpenPLC Editor v4 project, add any STRING variable to a PROGRAM (or a STRING field inside a user-defined STRUCT), and expose it via the OPC UA server device config.
  2. Compile and deploy to Runtime v4.1.10.
  3. In the program logic, assign a literal to the variable, e.g. MY_STRING := 'hello';, every scan.
  4. Connect an OPC UA client (e.g. UaExpert) to the Runtime's OPC UA server and browse to the node.

Expected: the node's value reflects 'hello'.
Actual: the node's value is always an empty string.

  1. From the OPC UA client, write a non-empty string to the same node (assuming the program does not overwrite it, or comment out the assignment first).

Expected: the write succeeds and the program can read back the new value.
Actual: the write has no effect on the program's memory; the Runtime log shows [ERROR]: debug_write(<arr>, <elem>) failed for that variable's address.

Impact

Any project design that relies on STRING variables as part of the OPC UA data contract (telemetry fields, text status/log fields, or any scheme that packs multiple values into a delimited string to reduce OPC UA node count) is currently non-functional for both directions (server → client and client → server) — the value is unconditionally empty on read, and writes are unconditionally dropped. Non-string scalar and array-of-non-string types are unaffected, since _ctype_for() correctly maps them to a ctypes scalar.

Suggested fix direction

Implement the STRING/WSTRING case in the debug memory-access surface referenced by the comments (debug_dispatch.hpp, "Phase 4a"): a length-prefixed or null-terminated variable-length read/write path through debug_read/debug_write, analogous to the existing fixed-size scalar path, with a defined maximum length matching the Editor's STRING size limit (126 characters).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions