Skip to content

Commit

Permalink
load_data_type_definitions: Accept OptionSet / Enumeration defined vi…
Browse files Browse the repository at this point in the history
…a StructureDefinition (FreeOpcUa#1383)

* When reading Enumeration and OptionSet type definitions, also accept StructureDefinition instance.

This kind of type definition is "unclean" but not strictly forbidden by the standard.
Requires guessing of actual enum values:
- Count starting at 1 for Enums
- 0x1, 0x2, 0x4, ... for OptionSet

* add test for OptionSet defined via StructureDefinition
  • Loading branch information
loehnertj authored and Dmitrij Vinokour committed Sep 19, 2023
1 parent e43f69e commit 05500d5
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 5 deletions.
22 changes: 17 additions & 5 deletions asyncua/common/structures104.py
Original file line number Diff line number Diff line change
Expand Up @@ -526,11 +526,23 @@ class {name}({enum_type}):
'''
"""

for sfield in edef.Fields:
name = clean_name(sfield.Name)
value = sfield.Value if not option_set else (1 << sfield.Value)
code += f" {name} = {value}\n"
for n, sfield in enumerate(edef.Fields):
fieldname = clean_name(sfield.Name)
if hasattr(sfield, "Value"):
value = sfield.Value if not option_set else (1 << sfield.Value)
else:
# Some servers represent the datatype as StructureDefinition instead of EnumDefinition.
# In this case the Value attribute is missing and we must guess.
# XXX: Assuming that counting starts with 1 for enumerations, which is by no means guaranteed.
value = n+1 if not option_set else (1 << n)
if n==0:
_logger.warning(
"%s type %s: guessing field values since the server does not provide them.",
"OptionSet" if option_set else "Enumeration",
name
)

code += f" {fieldname} = {value}\n"
return code


Expand Down
21 changes: 21 additions & 0 deletions tests/test_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -1280,6 +1280,27 @@ async def test_custom_option_set(opc):
val = await var.read_value()
assert val == (1 << 2) | (1 << 1)

async def test_custom_option_set_as_structure_definition(opc):
"""Option set defined via structure definition is understood.
This is arguably outside the standard but observed on some servers.
"""
idx = 4
server = opc.opc
await new_enum(opc.opc, idx, "MyOptionSet", ["tata", "titi", "toto", "None"], True)
sdef = ua.StructureDefinition()
for name in ["tata", "titi", "toto", "None"]:
field = ua.StructureField()
field.Name = name
sdef.Fields.append(field)
dtype = await server.nodes.option_set_type.add_data_type(idx, name)
await dtype.write_data_type_definition(sdef)

await opc.opc.load_data_type_definitions()
assert ua.MyOptionSet.toto | ua.MyOptionSet.titi == ua.MyOptionSet((1 << 2) | (1 << 1))
var = await opc.opc.nodes.objects.add_variable(idx, "my_option", ua.MyOptionSet.toto | ua.MyOptionSet.titi)
val = await var.read_value()
assert val == (1 << 2) | (1 << 1)

async def test_custom_struct_(opc):
idx = 4
Expand Down

0 comments on commit 05500d5

Please sign in to comment.