diff --git a/asyncua/common/structures104.py b/asyncua/common/structures104.py index ce98ac726..dd920ace3 100644 --- a/asyncua/common/structures104.py +++ b/asyncua/common/structures104.py @@ -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 diff --git a/tests/test_common.py b/tests/test_common.py index 6eb4a3722..e41ac0011 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -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