99import re
1010import logging
1111# The next two imports are for generated code
12+ from collections import namedtuple
1213from datetime import datetime
1314import uuid
1415from enum import Enum , IntEnum , EnumMeta
16+ from typing import Any , Union
1517
1618from lxml import objectify
1719
2123
2224logger = logging .getLogger (__name__ )
2325
24-
26+ # TODO: use non-empty fields for testing.
2527def 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):
8284class 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):
128142class 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+
140190class 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
228289from datetime import datetime
290+ from enum import IntEnum
229291import uuid
230292
231293from opcua import ua
0 commit comments