Writing a value to a REAL or LREAL PLC variable via OPC UA client results in a completely wrong value being stored. The INT type is not affected.
Steps to Reproduce
I am using: OpenPLC Runtime v4(latest) and OpenPLC.Editor-4.2.10. Running OpenPLC Runtime v4 in Docker.
docker pull ghcr.io/autonomy-logic/openplc-runtime:latest && \
docker run -d \
--name openplc-runtime \
--restart unless-stopped \
--publish 8443:8443 \
--publish 1502:502 \
--publish 14840:4840 \
--cap-add SYS_NICE \
--cap-add SYS_RESOURCE \
--memory 2g \
--memory-swap 2g \
--pids-limit 256 \
--security-opt no-new-privileges:true \
--log-driver json-file \
--log-opt max-size=5m \
--log-opt max-file=2 \
--volume /mnt/data/openplc/runtime:/var/run/runtime \
"$(docker image inspect ghcr.io/autonomy-logic/openplc-runtime:latest --format='{{index .RepoDigests 0}}')"
- Create a PLC program with three variables:
my_int : INT;
my_real : REAL;
my_lreal: LREAL;
- Assign
42 to each variable in the program body.
- Start the PLC in OpenPLC Runtime v4.
- Connect an OPC UA client and write the value
42 to each variable node.
- Read back the values.
Expected Behavior
| Variable |
Written |
Read back |
INT |
42 |
42 |
REAL |
42.0 |
42.0 |
LREAL |
42.0 |
42.0 |
Actual Behavior
| Variable |
Written |
Read back |
INT |
42 |
42 ✅ |
REAL |
42.0 |
~1.109918e+09 ❌ |
LREAL |
42.0 |
~4.631e+18 ❌ |
Environment
- OpenPLC Runtime v4 (latest)
- OpenPLC Editor 4.2.10
Root Cause Analysis
The bug is in core/src/drivers/plugins/python/opcua/opcua_utils.py, function convert_value_for_plc().
The write path is:
OPC UA client writes float 42.0
→ synchronization.py: calls convert_value_for_plc("REAL", 42.0)
→ opcua_memory.py: calls debug_write_value(..., result)
→ C layer: stores raw bytes in PLC variable
The bug is a double conversion. convert_value_for_plc() reinterprets the IEEE 754 bit pattern of 42.0 as an integer:
# convert_value_for_plc() current broken code
elif datatype.upper() in ["FLOAT", "REAL"]:
if isinstance(value, float):
return struct.unpack('I', struct.pack('f', value))[0]
# 42.0 → b'\x00\x00\x28\x42' → 1109917696
Then debug_write_value() in opcua_memory.py treats that integer numerically when constructing the ctypes value:
encoded = ctypes.c_float(1109917696)
# Numeric int→float conversion: 1109917696.0 ≈ 1.109918e+09 ❌
The same pattern affects LREAL:
# 42.0 → struct.unpack('Q', struct.pack('d', 42.0))[0] → 4638355772470722560
# ctypes.c_double(4638355772470722560) ≈ 4.638e+18 ❌
INT is unaffected because its branch does not use struct.pack/unpack it passes the integer value directly.
The read path (PLC -> OPC UA) works correctly because debug_read_value() returns an actual Python float via ctypes.c_float.value, and convert_value_for_opcua() simply does return float(value) for that case.
The LREAL bug was introduced in commit 3df551aa by copying the same broken pattern from the REAL branch.
Proposed Fix
In convert_value_for_plc(), simply return float(value) for REAL and LREAL. The debug_write_value() function already handles correct IEEE 754 encoding via ctypes internally:
# convert_value_for_plc() fix
elif datatype.upper() in ["FLOAT", "REAL"]:
return float(value) # debug_write_value will call ctypes.c_float(42.0) → correct bytes
elif datatype.upper() == "LREAL":
return float(value) # debug_write_value will call ctypes.c_double(42.0) → correct bytes
Forum topic: https://edge.autonomylogic.com/forum/thread/error-changing-values-via-opc-ua
Writing a value to a
REALorLREALPLC variable via OPC UA client results in a completely wrong value being stored. TheINTtype is not affected.Steps to Reproduce
I am using: OpenPLC Runtime v4(latest) and OpenPLC.Editor-4.2.10. Running OpenPLC Runtime v4 in Docker.
42to each variable in the program body.42to each variable node.Expected Behavior
INTREALLREALActual Behavior
INTREALLREALEnvironment
Root Cause Analysis
The bug is in
core/src/drivers/plugins/python/opcua/opcua_utils.py, functionconvert_value_for_plc().The write path is:
The bug is a double conversion.
convert_value_for_plc()reinterprets the IEEE 754 bit pattern of42.0as an integer:Then
debug_write_value()inopcua_memory.pytreats that integer numerically when constructing the ctypes value:The same pattern affects
LREAL:INTis unaffected because its branch does not usestruct.pack/unpackit passes the integer value directly.The read path (PLC -> OPC UA) works correctly because
debug_read_value()returns an actual Pythonfloatviactypes.c_float.value, andconvert_value_for_opcua()simply doesreturn float(value)for that case.The
LREALbug was introduced in commit3df551aaby copying the same broken pattern from theREALbranch.Proposed Fix
In
convert_value_for_plc(), simply returnfloat(value)forREALandLREAL. Thedebug_write_value()function already handles correct IEEE 754 encoding via ctypes internally:Forum topic: https://edge.autonomylogic.com/forum/thread/error-changing-values-via-opc-ua