Skip to content

Commit 96a8ea6

Browse files
David Chenoroulet
authored andcommitted
support optional fields (which seems to be a combination of bit + switch)
update tests so that they both register + verify nested objects are working clean up tests. is redundant and messing up a more natural path pattern for running tests simplify logic with BitFieldState
1 parent 1b85c61 commit 96a8ea6

12 files changed

+822
-48
lines changed

opcua/common/structures.py

Lines changed: 80 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99
import re
1010
import logging
1111
# The next two imports are for generated code
12+
from collections import namedtuple
1213
from datetime import datetime
1314
import uuid
1415
from enum import Enum, IntEnum, EnumMeta
16+
from typing import Any, Union
1517

1618
from lxml import objectify
1719

@@ -21,13 +23,13 @@
2123

2224
logger = logging.getLogger(__name__)
2325

24-
26+
# TODO: use non-empty fields for testing.
2527
def get_default_value(uatype, enums):
26-
if uatype == "String":
27-
return "None"
28+
if uatype in ("String", "CharArray"):
29+
return "''"
2830
elif uatype == "Guid":
2931
return "uuid.uuid4()"
30-
elif uatype in ("ByteString", "CharArray", "Char"):
32+
elif uatype in ("ByteString", "Char"):
3133
return "b''"
3234
elif uatype == "Boolean":
3335
return "True"
@@ -82,9 +84,17 @@ def __init__(self, name, value):
8284
class Struct(object):
8385
def __init__(self, name):
8486
self.name = _clean_name(name)
87+
self.bit_mapping = {}
8588
self.fields = []
8689
self.typeid = None
8790

91+
def get_ua_switches(self):
92+
ua_switches = {}
93+
for field in self.fields:
94+
if field.is_switch():
95+
ua_switches[field.name] = self.bit_mapping.get(field.switch_name)
96+
return ua_switches
97+
8898
def __str__(self):
8999
return "Struct(name={}, fields={}".format(self.name, self.fields)
90100
__repr__ = __str__
@@ -99,15 +109,19 @@ class {0}(object):
99109
'''
100110
101111
""".format(self.name)
102-
112+
ua_switches = self.get_ua_switches()
113+
if ua_switches:
114+
code += " ua_switches = {\n"
115+
for field_name, encode_tuple in self.get_ua_switches().items():
116+
code += " '{}': {}, \n".format(field_name, encode_tuple)
117+
code += " }\n"
103118
code += " ua_types = [\n"
104119
for field in self.fields:
105120
prefix = "ListOf" if field.array else ""
106121
uatype = prefix + field.uatype
107122
if uatype == "ListOfChar":
108123
uatype = "String"
109124
code += " ('{}', '{}'),\n".format(field.name, uatype)
110-
111125
code += " ]"
112126
code += """
113127
def __str__(self):
@@ -128,15 +142,51 @@ def __init__(self):
128142
class Field(object):
129143
def __init__(self, name):
130144
self.name = name
145+
self.switch_name = ""
131146
self.uatype = None
132-
self.value = None
147+
self.value = None # e.g: ua.Int32(0)
133148
self.array = False
134149

150+
def is_switch(self):
151+
return len(self.switch_name) > 0
152+
135153
def __str__(self):
136154
return "Field(name={}, uatype={}".format(self.name, self.uatype)
137155
__repr__ = __str__
138156

139157

158+
class BitFieldState:
159+
def __init__(self):
160+
self.encoding_field: Union[Field, None] = None
161+
self.bit_size = 0
162+
self.bit_offset = 0
163+
self.encoding_field_counter = 0
164+
165+
def add_bit(self, length: int) -> Union[Field, None]:
166+
""" Returns field if a new one was added. Else None """
167+
if not self.encoding_field:
168+
return self.reset_encoding_field()
169+
else:
170+
if self.bit_size + length > 32:
171+
return self.reset_encoding_field()
172+
else:
173+
self.bit_size += length
174+
self.bit_offset += 1
175+
return None
176+
177+
def reset_encoding_field(self) -> Field:
178+
field = Field(f"BitEncoding{self.encoding_field_counter}")
179+
field.uatype = "UInt32"
180+
self.encoding_field = field
181+
self.bit_offset = 0
182+
self.encoding_field_counter += 1
183+
return field
184+
185+
def get_bit_info(self) -> (str, int):
186+
""" With the field name and bit offset, we can extract the bit later."""
187+
return self.encoding_field.name, self.bit_offset
188+
189+
140190
class StructGenerator(object):
141191
def __init__(self):
142192
self.model = []
@@ -165,22 +215,33 @@ def _make_model(self, root):
165215
for child in root.iter("{*}StructuredType"):
166216
struct = Struct(child.get("Name"))
167217
array = False
218+
bit_state = BitFieldState()
168219
for xmlfield in child.iter("{*}Field"):
169220
name = xmlfield.get("Name")
221+
clean_name = _clean_name(name)
170222
if name.startswith("NoOf"):
171223
array = True
172224
continue
173-
field = Field(_clean_name(name))
174-
field.uatype = xmlfield.get("TypeName")
175-
if ":" in field.uatype:
176-
field.uatype = field.uatype.split(":")[1]
177-
field.uatype = _clean_name(field.uatype)
178-
field.value = get_default_value(field.uatype, enums)
179-
if array:
180-
field.array = True
181-
field.value = []
182-
array = False
183-
struct.fields.append(field)
225+
_type = xmlfield.get("TypeName")
226+
if ":" in _type:
227+
_type = _type.split(":")[1]
228+
_type = _clean_name(_type)
229+
if _type == 'Bit':
230+
bit_length = int(xmlfield.get("Length", 1)) # Will longer bits be used?
231+
field = bit_state.add_bit(bit_length)
232+
# Whether or not a new encoding field was added, we want to store the current one.
233+
struct.bit_mapping[name] = (bit_state.encoding_field.name, bit_state.bit_offset)
234+
else:
235+
field = Field(clean_name)
236+
field.uatype = _type
237+
field.switch_name = xmlfield.get('SwitchField', "")
238+
if field:
239+
field.value = get_default_value(field.uatype, enums)
240+
if array:
241+
field.array = True
242+
field.value = []
243+
array = False
244+
struct.fields.append(field)
184245
self.model.append(struct)
185246

186247
def save_to_file(self, path, register=False):
@@ -226,6 +287,7 @@ def _make_header(self, _file):
226287
'''
227288
228289
from datetime import datetime
290+
from enum import IntEnum
229291
import uuid
230292
231293
from opcua import ua

opcua/ua/uatypes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1010,7 +1010,7 @@ def get_default_value(vtype):
10101010
raise RuntimeError("function take a uatype as argument, got:", vtype)
10111011

10121012

1013-
# These dictionnaries are used to register extensions classes for automatic
1013+
# These dictionaries are used to register extensions classes for automatic
10141014
# decoding and encoding
10151015
extension_object_classes = {}
10161016
extension_object_ids = {}

run-tests.sh

Lines changed: 0 additions & 2 deletions
This file was deleted.

0 commit comments

Comments
 (0)