Commit 75bc0049 authored by Alexander Schrode's avatar Alexander Schrode Committed by oroulet

rework struct1_04 resoultion

parent 59cf26d7
...@@ -154,7 +154,7 @@ def get_default_value(uatype, enums=None): ...@@ -154,7 +154,7 @@ def get_default_value(uatype, enums=None):
return f"ua.{uatype}()" return f"ua.{uatype}()"
def make_structure_code(data_type, struct_name, sdef): def make_structure_code(data_type, struct_name, sdef, log_error=True):
""" """
given a StructureDefinition object, generate Python code given a StructureDefinition object, generate Python code
""" """
...@@ -193,9 +193,11 @@ class {struct_name}{base_class}: ...@@ -193,9 +193,11 @@ class {struct_name}{base_class}:
uatype = ua.extension_objects_by_datatype[sfield.DataType].__name__ uatype = ua.extension_objects_by_datatype[sfield.DataType].__name__
elif sfield.DataType in ua.enums_by_datatype: elif sfield.DataType in ua.enums_by_datatype:
uatype = ua.enums_by_datatype[sfield.DataType].__name__ uatype = ua.enums_by_datatype[sfield.DataType].__name__
elif sfield.DataType in ua.basetype_by_datatype:
uatype = ua.basetype_by_datatype[sfield.DataType]
else: else:
# FIXME: we are probably missing many custom tyes here based on builtin types if log_error:
# maybe we can use ua_utils.get_base_data_type() logger.error(f"Unknown datatype for field: {sfield} in structure:{struct_name}, please report")
raise RuntimeError(f"Unknown datatype for field: {sfield} in structure:{struct_name}, please report") raise RuntimeError(f"Unknown datatype for field: {sfield} in structure:{struct_name}, please report")
if sfield.ValueRank >= 0: if sfield.ValueRank >= 0:
...@@ -238,7 +240,7 @@ class {struct_name}{base_class}: ...@@ -238,7 +240,7 @@ class {struct_name}{base_class}:
return code return code
async def _generate_object(name, sdef, data_type=None, env=None, enum=False, option_set=False): async def _generate_object(name, sdef, data_type=None, env=None, enum=False, option_set=False, log_fail=True):
""" """
generate Python code and execute in a new environment generate Python code and execute in a new environment
return a dict of structures {name: class} return a dict of structures {name: class}
...@@ -271,11 +273,12 @@ async def _generate_object(name, sdef, data_type=None, env=None, enum=False, opt ...@@ -271,11 +273,12 @@ async def _generate_object(name, sdef, data_type=None, env=None, enum=False, opt
if enum: if enum:
code = make_enum_code(name, sdef, option_set) code = make_enum_code(name, sdef, option_set)
else: else:
code = make_structure_code(data_type, name, sdef) code = make_structure_code(data_type, name, sdef, log_error=log_fail)
logger.debug("Executing code: %s", code) logger.debug("Executing code: %s", code)
try: try:
exec(code, env) exec(code, env)
except Exception: except Exception:
if log_fail:
logger.exception("Failed to execute auto-generated code from UA datatype: %s", code) logger.exception("Failed to execute auto-generated code from UA datatype: %s", code)
raise raise
return env return env
...@@ -302,10 +305,13 @@ class DataTypeSorter: ...@@ -302,10 +305,13 @@ class DataTypeSorter:
async def _recursive_parse(server, base_node, dtypes, parent_sdef=None, add_existing=False): async def _recursive_parse(server, base_node, dtypes, parent_sdef=None, add_existing=False):
for desc in await base_node.get_children_descriptions(refs=ua.ObjectIds.HasSubtype): ch = await base_node.get_children_descriptions(refs=ua.ObjectIds.HasSubtype)
for desc in ch:
sdef = await _read_data_type_definition(server, desc, read_existing=add_existing) sdef = await _read_data_type_definition(server, desc, read_existing=add_existing)
if not sdef: if clean_name(desc.BrowseName.Name) == 'Union':
continue # Union don't contain a type defintion but there could be subtypes
await _recursive_parse(server, server.get_node(desc.NodeId), dtypes, parent_sdef, add_existing=add_existing)
elif sdef is not None:
name = clean_name(desc.BrowseName.Name) name = clean_name(desc.BrowseName.Name)
if parent_sdef: if parent_sdef:
for sfield in reversed(parent_sdef.Fields): for sfield in reversed(parent_sdef.Fields):
...@@ -339,25 +345,85 @@ async def load_custom_struct(node: Node) -> Any: ...@@ -339,25 +345,85 @@ async def load_custom_struct(node: Node) -> Any:
return env[name] return env[name]
async def _recursive_parse_basedatatypes(server, base_node, parent_datatype, new_alias) -> Any:
for desc in await base_node.get_children_descriptions(refs=ua.ObjectIds.HasSubtype):
name = clean_name(desc.BrowseName.Name)
if parent_datatype not in 'Number':
# Don't insert Number alias, they should be allready insert because they have to be basetypes allready
if not hasattr(ua, name):
env = make_basetype_code(name, parent_datatype)
ua.register_basetype(name, desc.NodeId, parent_datatype)
new_alias[name] = env[name]
await _recursive_parse_basedatatypes(server, server.get_node(desc.NodeId), name, new_alias)
def make_basetype_code(name, parent_datatype):
"""
alias basetypes
"""
code = f"""
{name} = ua.{parent_datatype}
"""
env = {}
env['ua'] = ua
logger.debug("Executing code: %s", code)
try:
exec(code, env)
except Exception:
logger.exception("Failed to execute auto-generated code from UA datatype: %s", code)
raise
return env
async def _load_base_datatypes(server: Union["Server", "Client"]) -> Any:
new_alias = {}
descriptions = await server.nodes.base_data_type.get_children_descriptions()
for desc in descriptions:
name = clean_name(desc.BrowseName.Name)
if name not in ['Structure', 'Enumeration']:
await _recursive_parse_basedatatypes(server, server.get_node(desc.NodeId), name, new_alias)
return new_alias
async def load_data_type_definitions(server: Union["Server", "Client"], base_node: Node = None, overwrite_existing=False) -> Dict: async def load_data_type_definitions(server: Union["Server", "Client"], base_node: Node = None, overwrite_existing=False) -> Dict:
""" """
Read DataTypeDefition attribute on all Structure and Enumeration defined Read DataTypeDefition attribute on all Structure and Enumeration defined
on server and generate Python objects in ua namespace to be used to talk with server on server and generate Python objects in ua namespace to be used to talk with server
""" """
new_objects = await load_enums(server) # we need all enums to generate structure code new_objects = await _load_base_datatypes(server) # we need to load all basedatatypes alias first
new_objects.update(await load_enums(server)) # we need all enums to generate structure code
new_objects.update(await load_enums(server, server.nodes.option_set_type, True)) # also load all optionsets new_objects.update(await load_enums(server, server.nodes.option_set_type, True)) # also load all optionsets
if base_node is None: if base_node is None:
base_node = server.nodes.base_structure_type base_node = server.nodes.base_structure_type
dtypes = [] dtypes = []
await _recursive_parse(server, base_node, dtypes, add_existing=overwrite_existing) await _recursive_parse(server, base_node, dtypes, add_existing=overwrite_existing)
dtypes.sort() dtypes.sort()
retries = 10
for cnt in range(retries):
# Retry to resolve datatypes
failed_types = []
log_ex = retries == cnt + 1
for dts in dtypes: for dts in dtypes:
try: try:
env = await _generate_object(dts.name, dts.sdef, data_type=dts.data_type) env = await _generate_object(dts.name, dts.sdef, data_type=dts.data_type, log_fail=log_ex)
ua.register_extension_object(dts.name, dts.encoding_id, env[dts.name], dts.data_type) ua.register_extension_object(dts.name, dts.encoding_id, env[dts.name], dts.data_type)
new_objects[dts.name] = env[dts.name] # type: ignore new_objects[dts.name] = env[dts.name] # type: ignore
except NotImplementedError: except NotImplementedError:
logger.exception("Structure type %s not implemented", dts.sdef) logger.exception("Structure type %s not implemented", dts.sdef)
except AttributeError:
# Failed to resolve datatypes
failed_types.append(dts)
if log_ex:
raise
except RuntimeError:
# Failed to resolve datatypes
failed_types.append(dts)
if log_ex:
raise
if not failed_types:
break
dtypes = failed_types
return new_objects return new_objects
......
...@@ -1062,6 +1062,23 @@ def get_default_value(vtype): ...@@ -1062,6 +1062,23 @@ def get_default_value(vtype):
raise RuntimeError(f"function take a uatype as argument, got: {vtype}") raise RuntimeError(f"function take a uatype as argument, got: {vtype}")
basetype_by_datatype = {}
basetype_datatypes = {}
# register of alias of basetypes
def register_basetype(name, nodeid, class_type):
"""
Register a new allias of basetypes for automatic decoding and make them available in ua module
"""
logger.info("registring new basetype alias: %s %s %s", name, nodeid, class_type)
basetype_by_datatype[nodeid] = class_type
basetype_datatypes[class_type] = nodeid
import asyncua.ua
setattr(asyncua.ua, name, class_type)
# register of custom enums (Those loaded with load_enums()) # register of custom enums (Those loaded with load_enums())
enums_by_datatype = {} enums_by_datatype = {}
enums_datatypes = {} enums_datatypes = {}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment