Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,25 @@
# Permission types for variables
PermissionType = Literal["r", "w", "rw"]

# Valid datatypes for OPC-UA variables
# Valid datatypes for OPC-UA variables (IEC 61131-3 base types)
# This list must match the base types supported by openplc-editor
VALID_DATATYPES = frozenset([
"BOOL", "BYTE",
"INT", "DINT", "LINT", "INT32",
"FLOAT", "REAL",
# Boolean
"BOOL",
# Signed integers
"SINT", "INT", "DINT", "LINT",
# Unsigned integers
"USINT", "UINT", "UDINT", "ULINT",
# Floating point
"REAL", "LREAL",
# Bit strings
"BYTE", "WORD", "DWORD", "LWORD",
# String
"STRING",
# TIME-related types (IEC 61131-3)
# Time-related types
"TIME", "DATE", "TOD", "DT",
# Legacy/alternative names (for backward compatibility)
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment indicates these are 'Legacy/alternative names' but doesn't specify which standard types they map to. Consider documenting that INT32 maps to DINT and FLOAT maps to REAL for clarity.

Suggested change
# Legacy/alternative names (for backward compatibility)
# Legacy/alternative names (for backward compatibility):
# INT32 -> DINT, FLOAT -> REAL

Copilot uses AI. Check for mistakes.
"INT32", "FLOAT",
])


Expand Down Expand Up @@ -466,20 +477,40 @@ def collect_field_indices(fields: List[VariableField]) -> List[int]:
raise ValueError(f"Duplicate indices found in plugin '{plugin.name}'")

# Validate datatypes
# Helper to validate datatypes recursively for nested fields
# Only leaf fields (those without nested children) are validated
def validate_field_datatypes(
fields: List[VariableField],
struct_node_id: str,
plugin_name: str,
path: str = ""
) -> None:
for field in fields:
# Build full path for better error messages
current_path = f"{path}.{field.name}" if path else field.name
if field.fields:
# Complex type with nested fields - recurse into children
# Don't validate the parent's datatype (e.g., TON, TOF, custom FB)
validate_field_datatypes(
field.fields, struct_node_id, plugin_name, current_path
)
else:
# Leaf field - validate its datatype
if not field.datatype or field.datatype.upper() not in VALID_DATATYPES:
raise ValueError(
f"Invalid datatype '{field.datatype}' for field '{current_path}' "
f"in struct '{struct_node_id}' in plugin '{plugin_name}'. "
f"Valid types: {sorted(VALID_DATATYPES)}"
)

for var in address_space.variables:
if var.datatype.upper() not in VALID_DATATYPES:
raise ValueError(
f"Invalid datatype '{var.datatype}' for variable '{var.node_id}' "
f"in plugin '{plugin.name}'. Valid types: {sorted(VALID_DATATYPES)}"
)
for struct in address_space.structures:
for field in struct.fields:
if field.datatype.upper() not in VALID_DATATYPES:
raise ValueError(
f"Invalid datatype '{field.datatype}' for field '{field.name}' "
f"in struct '{struct.node_id}' in plugin '{plugin.name}'. "
f"Valid types: {sorted(VALID_DATATYPES)}"
)
validate_field_datatypes(struct.fields, struct.node_id, plugin.name)
for arr in address_space.arrays:
if arr.datatype.upper() not in VALID_DATATYPES:
raise ValueError(
Expand Down