refactor(nc): Use UA_decodeXml to get the value at runtime from the original XML

This commit is contained in:
Julius Pfrommer 2025-03-03 17:33:40 +01:00 committed by Julius Pfrommer
parent a57d09955b
commit bce2e3b6f3
10 changed files with 192 additions and 1630 deletions

View File

@ -127,12 +127,6 @@
<Reference ReferenceType="HasTypeDefinition">i=63</Reference>
<Reference ReferenceType="Organizes" IsForward="false">ns=1;i=5100</Reference>
</References>
<Value>
<ListOfInt32>
<Int32>1</Int32>
<Int32>2</Int32>
</ListOfInt32>
</Value>
</UAVariable>
<UAVariable DataType="Int32" NodeId="ns=1;i=8006" BrowseName="1:Int32_OneOrMoreDim_noInit_ValueRank=0" AccessLevel="3" ValueRank="0">
<DisplayName>Int32_scalar_noInit_ValueRank=0</DisplayName>
@ -252,7 +246,7 @@
<uax:ListOfExtensionObject>
<uax:ExtensionObject>
<uax:TypeId>
<uax:Identifier>i=0</uax:Identifier>
<uax:Identifier>ns=1;i=10001</uax:Identifier>
</uax:TypeId>
<uax:Body>
<Point xmlns="http://yourorganisation.org/test/Types.xsd">
@ -263,7 +257,7 @@
</uax:ExtensionObject>
<uax:ExtensionObject>
<uax:TypeId>
<uax:Identifier>i=0</uax:Identifier>
<uax:Identifier>ns=1;i=10001</uax:Identifier>
</uax:TypeId>
<uax:Body>
<Point xmlns="http://yourorganisation.org/test/Types.xsd">
@ -274,7 +268,7 @@
</uax:ExtensionObject>
<uax:ExtensionObject>
<uax:TypeId>
<uax:Identifier>i=0</uax:Identifier>
<uax:Identifier>ns=1;i=10001</uax:Identifier>
</uax:TypeId>
<uax:Body>
<Point xmlns="http://yourorganisation.org/test/Types.xsd">
@ -285,7 +279,7 @@
</uax:ExtensionObject>
<uax:ExtensionObject>
<uax:TypeId>
<uax:Identifier>i=0</uax:Identifier>
<uax:Identifier>ns=1;i=10001</uax:Identifier>
</uax:TypeId>
<uax:Body>
<Point xmlns="http://yourorganisation.org/test/Types.xsd">
@ -304,52 +298,58 @@
<Reference ReferenceType="HasComponent" IsForward="false">ns=1;i=5001</Reference>
</References>
<Value>
<uax:ListOfExtensionObject>
<uax:ExtensionObject>
<uax:TypeId>
<uax:Identifier>ns=1;i=10001</uax:Identifier>
</uax:TypeId>
<uax:Body>
<Point xmlns="http://yourorganisation.org/test/Types.xsd">
<x>1</x>
<y>2</y>
</Point>
</uax:Body>
</uax:ExtensionObject>
<uax:ExtensionObject>
<uax:TypeId>
<uax:Identifier>ns=1;i=10001</uax:Identifier>
</uax:TypeId>
<uax:Body>
<Point xmlns="http://yourorganisation.org/test/Types.xsd">
<x>3</x>
<y>4</y>
</Point>
</uax:Body>
</uax:ExtensionObject>
<uax:ExtensionObject>
<uax:TypeId>
<uax:Identifier>ns=1;i=10001</uax:Identifier>
</uax:TypeId>
<uax:Body>
<Point xmlns="http://yourorganisation.org/test/Types.xsd">
<x>5</x>
<y>6</y>
</Point>
</uax:Body>
</uax:ExtensionObject>
<uax:ExtensionObject>
<uax:TypeId>
<uax:Identifier>ns=1;i=10001</uax:Identifier>
</uax:TypeId>
<uax:Body>
<Point xmlns="http://yourorganisation.org/test/Types.xsd">
<x>7</x>
<y>8</y>
</Point>
</uax:Body>
</uax:ExtensionObject>
</uax:ListOfExtensionObject>
<Matrix>
<Dimensions>
<Int32>2</Int32>
<Int32>2</Int32>
</Dimensions>
<Elements>
<uax:ExtensionObject>
<uax:TypeId>
<uax:Identifier>ns=1;i=10001</uax:Identifier>
</uax:TypeId>
<uax:Body>
<Point xmlns="http://yourorganisation.org/test/Types.xsd">
<x>1</x>
<y>2</y>
</Point>
</uax:Body>
</uax:ExtensionObject>
<uax:ExtensionObject>
<uax:TypeId>
<uax:Identifier>ns=1;i=10001</uax:Identifier>
</uax:TypeId>
<uax:Body>
<Point xmlns="http://yourorganisation.org/test/Types.xsd">
<x>3</x>
<y>4</y>
</Point>
</uax:Body>
</uax:ExtensionObject>
<uax:ExtensionObject>
<uax:TypeId>
<uax:Identifier>ns=1;i=10001</uax:Identifier>
</uax:TypeId>
<uax:Body>
<Point xmlns="http://yourorganisation.org/test/Types.xsd">
<x>5</x>
<y>6</y>
</Point>
</uax:Body>
</uax:ExtensionObject>
<uax:ExtensionObject>
<uax:TypeId>
<uax:Identifier>ns=1;i=10001</uax:Identifier>
</uax:TypeId>
<uax:Body>
<Point xmlns="http://yourorganisation.org/test/Types.xsd">
<x>7</x>
<y>8</y>
</Point>
</uax:Body>
</uax:ExtensionObject>
</Elements>
</Matrix>
</Value>
</UAVariable>
<UADataType NodeId="ns=1;i=4002" BrowseName="1:SelfContainingUnion">
@ -440,14 +440,20 @@
<Reference ReferenceType="Organizes" IsForward="false">ns=1;i=5100</Reference>
</References>
<Value>
<uax:ListOfInt32>
<uax:Int32>11</uax:Int32>
<uax:Int32>21</uax:Int32>
<uax:Int32>31</uax:Int32>
<uax:Int32>12</uax:Int32>
<uax:Int32>22</uax:Int32>
<uax:Int32>32</uax:Int32>
</uax:ListOfInt32>
<Matrix>
<Dimensions>
<Int32>3</Int32>
<Int32>2</Int32>
</Dimensions>
<Elements>
<Int32>11</Int32>
<Int32>21</Int32>
<Int32>31</Int32>
<Int32>12</Int32>
<Int32>22</Int32>
<Int32>32</Int32>
</Elements>
</Matrix>
</Value>
</UAVariable>
<UADataType NodeId="ns=1;i=3003" BrowseName="1:PointWithArray">
@ -621,7 +627,7 @@
<Reference ReferenceType="Organizes" IsForward="false">ns=1;i=5100</Reference>
</References>
<Value>
<uax:ListOfString>
<uax:ListOfGuid>
<uax:Guid>
<uax:String>7822a391-1111-4a59-b08d-b70bc63fecec</uax:String>
</uax:Guid>
@ -631,7 +637,7 @@
<uax:Guid>
<uax:String>7822a391-3333-4a59-b08d-b70bc63fecec</uax:String>
</uax:Guid>
</uax:ListOfString>
</uax:ListOfGuid>
</Value>
</UAVariable>
<UAVariable DataType="DataSetMetaDataType" ParentNodeId="ns=1;i=5001" NodeId="ns=1;i=6021" BrowseName="TestDataSetMetaData">

View File

@ -411,7 +411,6 @@ function(ua_generate_nodeset)
${open62541_TOOLS_DIR}/nodeset_compiler/datatypes.py
${open62541_TOOLS_DIR}/nodeset_compiler/backend_open62541.py
${open62541_TOOLS_DIR}/nodeset_compiler/backend_open62541_nodes.py
${open62541_TOOLS_DIR}/nodeset_compiler/backend_open62541_datatypes.py
${UA_GEN_NS_FILE}
${UA_GEN_NS_DEPENDS_NS}
${GEN_BLACKLIST_DEPENDS}

View File

@ -9,7 +9,6 @@
### Copyright 2016-2017 (c) Stefan Profanter, fortiss GmbH
### Copyright 2021 (c) Wind River Systems, Inc.
from os.path import basename
import logging
import codecs
@ -187,7 +186,7 @@ _UA_END_DECLS
if len(code_global) > 0:
writec("\n".join(code_global))
writec("\n")
writec("\nstatic UA_StatusCode function_" + outfilebase + "_" + str(functionNumber) + "_begin(UA_Server *server, UA_UInt16* ns) {")
writec("\nstatic UA_StatusCode function_" + outfilebase + "_" + str(functionNumber) + "_begin(UA_Server *server, UA_NamespaceMapping *nsMapping) {")
if isinstance(node, MethodNode) or isinstance(node.parent, MethodNode):
writec("#ifdef UA_ENABLE_METHODCALLS")
writec(code)
@ -215,7 +214,7 @@ _UA_END_DECLS
writec("#endif /* UA_ENABLE_METHODCALLS */")
writec("}")
writec("\nstatic UA_StatusCode function_" + outfilebase + "_" + str(functionNumber) + "_finish(UA_Server *server, UA_UInt16* ns) {")
writec("\nstatic UA_StatusCode function_" + outfilebase + "_" + str(functionNumber) + "_finish(UA_Server *server, UA_NamespaceMapping *nsMapping) {")
if isinstance(node, MethodNode) or isinstance(node.parent, MethodNode):
writec("#ifdef UA_ENABLE_METHODCALLS")
@ -234,7 +233,6 @@ _UA_END_DECLS
functionNumber = functionNumber + 1
# Load generated types
for arr in typesArray:
if arr == "UA_TYPES":
@ -251,11 +249,29 @@ UA_StatusCode retVal = UA_STATUSCODE_GOOD;""" % (outfilebase))
# Generate namespaces (don't worry about duplicates)
writec("/* Use namespace ids generated by the server */")
writec("UA_UInt16 ns[" + str(len(nodeset.namespaces)) + "];")
writec("UA_UInt16 ns[" + str(len(nodeset.namespaces)+1) + "];")
for i, nsid in enumerate(nodeset.namespaces):
nsid = nsid.replace("\"", "\\\"")
writec("ns[" + str(i) + "] = UA_Server_addNamespace(server, \"" + nsid + "\");")
# Write the list of ns mappings
maxns = max(nodeset.namespaceMapping.keys()) + 1
if maxns < 2:
maxns = 2
mapping = [0] * maxns
mapping[1] = 1 # default
for k, v in nodeset.namespaceMapping.items():
mapping[k] = v
writec(f"UA_UInt16 nsMappingTable[{len(mapping)}]" + " = {" + ", ".join(map(lambda x: f"ns[{x}]", mapping)) + "};")
# Create the namespace mapping
writec("UA_NamespaceMapping nsMapping;")
writec("memset(&nsMapping, 0, sizeof(UA_NamespaceMapping));")
writec("nsMapping.local2remote = ns;")
writec(f"nsMapping.local2remoteSize = {len(nodeset.namespaces)};")
writec("nsMapping.remote2local = nsMappingTable;")
writec(f"nsMapping.remote2localSize = {len(mapping)};")
# Change namespaceIndex from the current namespace,
# but only if it defines its own data types, otherwise it is not necessary.
if len(typesArray) > 0:
@ -283,16 +299,16 @@ UA_StatusCode retVal = UA_STATUSCODE_GOOD;""" % (outfilebase))
if functionNumber > 0:
for i in range(0, functionNumber):
writec("retVal |= function_{outfilebase}_{idx}_begin(server, ns);". \
writec("retVal |= function_{outfilebase}_{idx}_begin(server, &nsMapping);". \
format(outfilebase=outfilebase, idx=str(i)))
if i in reftypes_functionNumbers:
writec("retVal |= function_{outfilebase}_{idx}_finish(server, ns);". \
writec("retVal |= function_{outfilebase}_{idx}_finish(server, &nsMapping);". \
format(outfilebase=outfilebase, idx=str(i)))
for i in reversed(range(0, functionNumber)):
if i in reftypes_functionNumbers:
continue
writec("retVal |= function_{outfilebase}_{idx}_finish(server, ns);". \
writec("retVal |= function_{outfilebase}_{idx}_finish(server, &nsMapping);". \
format(outfilebase=outfilebase, idx=str(i)))
writec("return retVal;\n}")

View File

@ -1,239 +0,0 @@
from datatypes import Boolean, Byte, SByte, \
Int16, UInt16, Int32, UInt32, Int64, UInt64, Float, Double, \
String, XmlElement, ByteString, Structure, ExtensionObject, LocalizedText, \
NodeId, ExpandedNodeId, DateTime, QualifiedName, StatusCode, \
DiagnosticInfo, Guid, BuiltinType
import datetime
import re
import logging
logger = logging.getLogger(__name__)
def generateBooleanCode(value):
if value:
return "true"
return "false"
# Strip invalid characters to create valid C identifiers (variable names etc):
def makeCIdentifier(value):
keywords = frozenset(["double", "int", "float", "char"])
sanitized = re.sub(r'[^\w]', '', value)
if sanitized in keywords:
return "_" + sanitized
else:
return sanitized
# Escape C strings:
def makeCLiteral(value):
return re.sub(r'(?<!\\)"', r'\\"', value.replace('\\', r'\\').replace('"', r'\"').replace('\n', r'\n').replace('\r', r''))
def splitStringLiterals(value, splitLength=500):
"""
Split a string literal longer than splitLength into smaller literals.
E.g. "Some very long text" will be split into "Some ver" "y long te" "xt"
On VS2008 there is a maximum allowed length of a single string literal.
"""
value = value.strip()
if len(value) < splitLength or splitLength == 0:
return "\"" + re.sub(r'(?<!\\)"', r'\\"', value) + "\""
ret = ""
tmp = value
while len(tmp) > splitLength:
ret += "\"" + tmp[:splitLength].replace('"', r'\"') + "\" "
tmp = tmp[splitLength:]
ret += "\"" + re.sub(r'(?<!\\)"', r'\\"', tmp) + "\" "
return ret
def generateStringCode(value, alloc=False):
value = makeCLiteral(value)
return "UA_STRING{}({})".format("_ALLOC" if alloc else "", splitStringLiterals(value))
def generateXmlElementCode(value, alloc=False):
value = makeCLiteral(value)
return "UA_XMLELEMENT{}({})".format("_ALLOC" if alloc else "", splitStringLiterals(value))
def generateByteStringCode(value, valueName, global_var_code, isPointer):
if isinstance(value, str):
# PY3 returns a byte array for b64decode, while PY2 returns a string.
# Therefore convert it to bytes
asciiarray = bytearray()
asciiarray.extend(value)
asciiarray = list(asciiarray)
else:
asciiarray = list(value)
asciiarraystr = str(asciiarray).rstrip(']').lstrip('[')
cleanValueName = re.sub(r"->", "__", re.sub(r"\.", "_", valueName))
global_var_code.append("static const UA_Byte {cleanValueName}_byteArray[{len}] = {{{data}}};".format(
len=len(asciiarray), data=asciiarraystr, cleanValueName=cleanValueName
))
# Cast away const with '(UA_Byte *)(void*)(uintptr_t)' since we know that UA_Server_addNode_begin will copy the content
return "{instance}{accessor}length = {len};\n{instance}{accessor}data = (UA_Byte *)(void*)(uintptr_t){cleanValueName}_byteArray;"\
.format(len=len(asciiarray), instance=valueName, cleanValueName=cleanValueName,
accessor='->' if isPointer else '.')
def generateLocalizedTextCode(value, alloc=False):
if value.text is None:
value.text = ""
vt = makeCLiteral(value.text)
return "UA_LOCALIZEDTEXT{}(\"{}\", {})".format("_ALLOC" if alloc else "", '' if value.locale is None else value.locale,
splitStringLiterals(vt))
def generateQualifiedNameCode(value, alloc=False,):
vn = makeCLiteral(value.name)
return "UA_QUALIFIEDNAME{}(ns[{}], {})".format("_ALLOC" if alloc else "",
str(value.ns), splitStringLiterals(vn))
def generateGuidCode(value):
if isinstance(value, str):
return f"UA_GUID(\"{value}\")"
if not value or len(value) != 5:
return "UA_GUID_NULL"
else:
return "UA_GUID(\"{}\")".format('-'.join(value))
def generateNodeIdCode(value):
if not value:
return "UA_NODEID_NUMERIC(0, 0)"
if value.i != None:
return "UA_NODEID_NUMERIC(ns[{}], {}LU)".format(value.ns, value.i)
elif value.s != None:
v = makeCLiteral(value.s)
return "UA_NODEID_STRING(ns[{}], \"{}\")".format(value.ns, v)
elif value.g != None:
return "UA_NODEID_GUID(ns[{}], {})".format(value.ns, generateGuidCode(value.gAsString()))
raise Exception(str(value) + " NodeID generation for bytestring NodeIDs not supported")
def generateExpandedNodeIdCode(value):
if value.i != None:
return "UA_EXPANDEDNODEID_NUMERIC(ns[{}], {}LU)".format(str(value.ns), str(value.i))
elif value.s != None:
vs = makeCLiteral(value.s)
return "UA_EXPANDEDNODEID_STRING(ns[{}], \"{}\")".format(str(value.ns), vs)
raise Exception(str(value) + " no NodeID generation for bytestring and guid..")
def generateDateTimeCode(value):
epoch = datetime.datetime.utcfromtimestamp(0)
mSecsSinceEpoch = int((value - epoch).total_seconds() * 1000.0)
return "( (UA_DateTime)(" + str(mSecsSinceEpoch) + " * UA_DATETIME_MSEC) + UA_DATETIME_UNIX_EPOCH)"
def lowerFirstChar(inputString):
return inputString[0].lower() + inputString[1:]
def generateNodeValueCode(prepend , node, instanceName, valueName, global_var_code, asIndirect=False, encRule=None, nodeset=None, idxList=None):
# TODO: The default values for the remaining data types still have to be added.
if type(node) in [Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Float, Double]:
if node.value is None:
if type(node) == Boolean:
node.value = False
elif type(node) == Double or type(node) == Float:
node.value = 0.0
else:
node.value = 0
if encRule is None:
return prepend + " = (UA_" + node.__class__.__name__ + ") " + str(node.value) + ";"
else:
return prepend + " = (UA_" + encRule.member_type.name + ") " + str(node.value) + ";"
elif isinstance(node, String):
return prepend + " = " + generateStringCode(node.value, alloc=asIndirect) + ";"
elif isinstance(node, XmlElement):
return prepend + " = " + generateXmlElementCode(node.value, alloc=asIndirect) + ";"
elif isinstance(node, ByteString):
# Basically the prepend must be passed to the generateByteStrongCode function so that the nested structures are
# generated correctly. In case of a pointer the valueName is used. This is for example the case with NS0
# (ns=0;i=8252)
valueName = valueName if prepend[0] == '*' else prepend
# replace whitespaces between tags and remove newlines
return prepend + " = UA_BYTESTRING_NULL;" if not node.value else generateByteStringCode(
node.value, valueName, global_var_code, isPointer=asIndirect)
# the replacements done here is just for the array form can be workable in C code. It doesn't couses any problem
# because the core data used here is already in byte form. So, there is no way we disturb it.
elif isinstance(node, LocalizedText):
return prepend + " = " + generateLocalizedTextCode(node, alloc=asIndirect) + ";"
elif isinstance(node, NodeId):
return prepend + " = " + generateNodeIdCode(node) + ";"
elif isinstance(node, ExpandedNodeId):
return prepend + " = " + generateExpandedNodeIdCode(node) + ";"
elif isinstance(node, DateTime):
return prepend + " = " + generateDateTimeCode(node.value) + ";"
elif isinstance(node, QualifiedName):
return prepend + " = " + generateQualifiedNameCode(node, alloc=asIndirect) + ";"
elif isinstance(node, StatusCode):
raise Exception("generateNodeValueCode for type " + node.__class__.name + " not implemented")
elif isinstance(node, DiagnosticInfo):
raise Exception("generateNodeValueCode for type " + node.__class__.name + " not implemented")
elif isinstance(node, Guid):
return prepend + " = " + generateGuidCode(node.value) + ";"
elif isinstance(node, ExtensionObject):
if asIndirect == False:
return prepend + " = *" + str(instanceName) + ";"
return prepend + " = " + str(instanceName) + ";"
elif isinstance(node, list):
code = []
if idxList is None:
raise Exception("No index was passed and the code generation cannot generate the array element")
if len(node) == 0:
return "\n".join(code)
# Code generation for structure arrays with fields of type Buildin.
# Example:
# Structure []
# | | |_ UInt32
# | |_ UInt32
# |_ UInt16
if isinstance(encRule.member_type, BuiltinType):
# Initialize the stack array
typeOfArray = encRule.member_type.name
arrayName = encRule.name
code.append("UA_STACKARRAY(UA_" + typeOfArray + ", " + arrayName+f", {len(node)});")
# memset is used here instead of UA_Init. Finding the dataType nodeID (to get the type array)
# would require searching whole nodeset to match the type name
code.append("memset({arrayName}, 0, sizeof(UA_{typeOfArray}) * {arrayLength});".format(arrayName=arrayName, typeOfArray=typeOfArray,
arrayLength=len(node)))
for idx,subv in enumerate(node):
code.append(generateNodeValueCode(arrayName + "[" + str(idx) + "]", subv, instanceName, valueName, global_var_code, asIndirect, encRule=encRule, idxList=idx))
code.append(prepend + f"Size = {len(node)};")
code.append(prepend + " = " + arrayName +";")
# Code generation for structure arrays with fields of different types.
# Example:
# Structure []
# | |_ String
# |_ Structure []
# | |_ Double
# |_ Double
else:
arrayName = encRule.name
for idx,subv in enumerate(node):
encField = encRule.member_type.members[idx].name
subEncRule = encRule.member_type.members[idx]
code.append(generateNodeValueCode(arrayName + "[" + str(idxList) + "]" + "." + encField, subv, instanceName, valueName, global_var_code, asIndirect, encRule=subEncRule, idxList=idx))
return "\n".join(code)
elif isinstance(node, Structure):
code = []
if encRule.is_array:
if len(node.value) == 0:
return "\n".join(code)
# Initialize the stack array
typeOfArray = encRule.member_type.name
arrayName = encRule.name
code.append("UA_STACKARRAY(UA_" + typeOfArray + ", " + arrayName+f", {len(node.value)});")
# memset is used here instead of UA_Init. Finding the dataType nodeID (to get the type array)
# would require searching whole nodeset to match the type name
code.append("memset({arrayName}, 0, sizeof(UA_{typeOfArray}) * {arrayLength});".format(arrayName=arrayName, typeOfArray=typeOfArray,
arrayLength=len(node.value)))
# Values is a list of lists
# The current index must be passed so that the code path for evaluating lists has the current index value and can generate the code correctly.
for idx,subv in enumerate(node.value):
encField = encRule.name
subEncRule = encRule
code.append(generateNodeValueCode(prepend + "." + lowerFirstChar(encField), subv, instanceName, valueName, global_var_code, asIndirect, encRule=subEncRule, idxList=idx))
code.append(prepend + f"Size = {len(node.value)};")
code.append(prepend + " = " + arrayName +";")
else:
for idx,subv in enumerate(node.value):
encField = encRule.member_type.members[idx].name
subEncRule = encRule.member_type.members[idx]
code.append(generateNodeValueCode(prepend + "." + lowerFirstChar(encField), subv, instanceName, valueName, global_var_code, asIndirect, encRule=subEncRule, idxList=idx))
return "\n".join(code)

View File

@ -5,21 +5,87 @@
### file, You can obtain one at http://mozilla.org/MPL/2.0/.
### Copyright 2014-2015 (c) TU-Dresden (Author: Chris Iatrou)
### Copyright 2014-2017 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
### Copyright 2014-2017, 2025 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
### Copyright 2016-2017 (c) Stefan Profanter, fortiss GmbH
### Copyright 2019 (c) Andrea Minosu
### Copyright 2018 (c) Jannis Volker
### Copyright 2018 (c) Ralph Lange
from datatypes import ExtensionObject, NodeId, StatusCode, DiagnosticInfo, Value
from datatypes import NodeId
from nodes import ReferenceTypeNode, ObjectNode, VariableNode, VariableTypeNode, MethodNode, ObjectTypeNode, DataTypeNode, ViewNode
from backend_open62541_datatypes import makeCIdentifier, generateLocalizedTextCode, generateQualifiedNameCode, generateNodeIdCode, \
generateExpandedNodeIdCode, generateNodeValueCode
import re
import logging
logger = logging.getLogger(__name__)
# Strip invalid characters to create valid C identifiers (variable names etc):
def makeCIdentifier(value):
keywords = frozenset(["double", "int", "float", "char"])
sanitized = re.sub(r'[^\w]', '', value)
if sanitized in keywords:
return "_" + sanitized
else:
return sanitized
# Escape C strings:
def makeCLiteral(value):
return re.sub(r'(?<!\\)"', r'\\"', value.replace('\\', r'\\').replace('"', r'\"').replace('\n', r'\n').replace('\r', r''))
def splitStringLiterals(value, splitLength=500):
"""
Split a string literal longer than splitLength into smaller literals.
E.g. "Some very long text" will be split into "Some ver" "y long te" "xt"
On VS2008 there is a maximum allowed length of a single string literal.
"""
value = value.strip()
if len(value) < splitLength or splitLength == 0:
return "\"" + re.sub(r'(?<!\\)"', r'\\"', value) + "\""
ret = ""
tmp = value
while len(tmp) > splitLength:
ret += "\"" + tmp[:splitLength].replace('"', r'\"') + "\" "
tmp = tmp[splitLength:]
ret += "\"" + re.sub(r'(?<!\\)"', r'\\"', tmp) + "\" "
return ret
def generateLocalizedTextCode(value, alloc=False):
if value.text is None:
value.text = ""
vt = makeCLiteral(value.text)
return "UA_LOCALIZEDTEXT{}(\"{}\", {})".format("_ALLOC" if alloc else "", '' if value.locale is None else value.locale, splitStringLiterals(vt))
def generateQualifiedNameCode(value, alloc=False,):
vn = makeCLiteral(value.name)
return "UA_QUALIFIEDNAME{}(UA_NamespaceMapping_local2Remote(nsMapping, {}), {})".format("_ALLOC" if alloc else "", str(value.ns), splitStringLiterals(vn))
def generateGuidCode(value):
if isinstance(value, str):
return f"UA_GUID(\"{value}\")"
if not value or len(value) != 5:
return "UA_GUID_NULL"
else:
return "UA_GUID(\"{}\")".format('-'.join(value))
def generateNodeIdCode(value):
if not value:
return "UA_NODEID_NUMERIC(0, 0)"
if value.i != None:
return f"UA_NODEID_NUMERIC(UA_NamespaceMapping_local2Remote(nsMapping, {value.ns}), {value.i}LU)"
elif value.s != None:
v = makeCLiteral(value.s)
return f"UA_NODEID_STRING(UA_NamespaceMapping_local2Remote(nsMapping, {value.ns}), \"{v}\")"
elif value.g != None:
return "UA_NODEID_GUID(UA_NamespaceMapping_local2Remote(nsMapping, {}), {})".format(value.ns, generateGuidCode(value.gAsString()))
raise Exception(str(value) + " NodeID generation for bytestring NodeIDs not supported")
def generateExpandedNodeIdCode(value):
if value.i != None:
return "UA_EXPANDEDNODEID_NUMERIC(UA_NamespaceMapping_local2Remote(nsMapping, {}), {}LU)".format(value.ns, str(value.i))
elif value.s != None:
vs = makeCLiteral(value.s)
return "UA_EXPANDEDNODEID_STRING(UA_NamespaceMapping_local2Remote(nsMapping, {}), \"{}\")".format(value.ns, vs)
raise Exception(str(value) + " no NodeID generation for bytestring and guid..")
#################
# Generate Code #
#################
@ -32,9 +98,6 @@ def generateNodeIdPrintable(node):
return re.sub('[^0-9a-z_]+', '_', CodePrintable.lower())
def generateNodeValueInstanceName(node, parent, arrayIndex):
return generateNodeIdPrintable(parent) + "_" + str(node.alias) + "_" + str(arrayIndex)
def generateReferenceCode(reference):
forwardFlag = "true" if reference.isForward else "false"
return "retVal |= UA_Server_addReference(server, %s, %s, %s, %s);" % \
@ -189,35 +252,24 @@ def generateCommonVariableCode(node, nodeset):
dataTypeNode = nodeset.getBaseDataType(nodeset.getDataTypeNode(node.dataType))
if dataTypeNode is None:
raise RuntimeError("Cannot get BaseDataType for dataType : " + str(node.dataType) + " of node " + node.browseName.name + " " + str(node.id))
raise RuntimeError("Cannot get BaseDataType for dataType : " + \
str(node.dataType) + " of node " + \
node.browseName.name + " " + str(node.id))
code.append("attr.dataType = %s;" % generateNodeIdCode(node.dataType))
if dataTypeNode.isEncodable():
if node.value is not None:
[code1, codeCleanup1, codeGlobal1] = generateValueCode(node.value, nodeset.nodes[node.id], nodeset)
code += code1
codeCleanup += codeCleanup1
codeGlobal += codeGlobal1
# #1978 Variant arrayDimensions are only required to properly decode multidimensional arrays
# (valueRank > 1) from data stored as one-dimensional array of arrayLength elements.
# One-dimensional arrays are already completely defined by arraylength attribute so setting
# also arrayDimensions, even if not explicitly forbidden, can confuse clients
if node.valueRank is not None and node.valueRank > 1 and len(node.arrayDimensions) == node.valueRank and len(node.value.value) > 0:
numElements = 1
hasZero = False
for v in node.arrayDimensions:
dim = int(v)
if dim > 0:
numElements = numElements * dim
else:
hasZero = True
if hasZero == False and len(node.value.value) == numElements:
code.append("attr.value.arrayDimensionsSize = attr.arrayDimensionsSize;")
code.append("attr.value.arrayDimensions = attr.arrayDimensions;")
elif node.value is not None:
code.append("/* Cannot encode the value */")
logger.warn("Cannot encode dataTypeNode: " + dataTypeNode.browseName.name + " for value of node " + node.browseName.name + " " + str(node.id))
if node.value:
xmlenc = [makeCLiteral(line) for line in node.value.toxml().splitlines()]
xmlenc = [(" " * (len(line) - len(line.lstrip()))) + "\"" + line.lstrip() + "\"" for line in xmlenc]
outxml = "\n".join(xmlenc)
code.append(f"UA_String xmlValue = UA_STRING({outxml});")
code.append("""UA_DecodeXmlOptions opts;
memset(&opts, 0, sizeof(UA_DecodeXmlOptions));
opts.unwrapped = true;
opts.namespaceMapping = nsMapping;
opts.customTypes = UA_Server_getConfig(server)->customDataTypes;
retVal |= UA_decodeXml(&xmlValue, &attr.value, &UA_TYPES[UA_TYPES_VARIANT], &opts);""")
codeCleanup.append("UA_Variant_clear(&attr.value);")
return [code, codeCleanup, codeGlobal]
@ -252,192 +304,6 @@ def generateVariableTypeNodeCode(node, nodeset):
return [code, codeCleanup, codeGlobal]
def lowerFirstChar(inputString):
return inputString[0].lower() + inputString[1:]
def generateExtensionObjectSubtypeCode(node, parent, nodeset, global_var_code, instanceName=None, isArrayElement=False):
code = [""]
codeCleanup = [""]
logger.debug("Building extensionObject for " + str(parent.id))
logger.debug("Encoding " + str(node.encodingRule))
parentDataType = nodeset.getDataTypeNode(parent.dataType)
parentDataTypeName = nodeset.getDataTypeNode(parent.dataType).browseName.name
if parentDataType.symbolicName is not None and parentDataType.symbolicName.value is not None:
parentDataTypeName = parentDataType.symbolicName.value
typeBrowseNode = makeCIdentifier(parentDataTypeName)
#TODO: review this
if typeBrowseNode == "NumericRange":
# in the stack we define a separate structure for the numeric range, but
# the value itself is just a string
typeBrowseNode = "String"
typeString = "UA_" + typeBrowseNode
if instanceName is None:
instanceName = generateNodeValueInstanceName(node, parent, 0)
code.append("UA_STACKARRAY(" + typeString + ", " + instanceName + ", 1);")
typeArr = nodeset.getDataTypeNode(parent.dataType).typesArray
typeArrayString = typeArr + "[" + typeArr + "_" + parentDataTypeName.upper() + "]"
code.append("UA_init({ref}{instanceName}, &{typeArrayString});".format(ref="&" if isArrayElement else "",
instanceName=instanceName,
typeArrayString=typeArrayString))
# Assign data to the struct contents
# Track the encoding rule definition to detect arrays and/or ExtensionObjects
values = node.value
if values == None:
values = []
for idx,subv in enumerate(values):
if subv is None:
continue
encField = node.encodingRule[idx].name
encRule = node.encodingRule[idx]
memberName = makeCIdentifier(lowerFirstChar(encField))
# Check if this is an array
accessor = "." if isArrayElement else "->"
if isinstance(subv, list):
if len(subv) == 0:
continue
logger.info("ExtensionObject contains array")
memberName = makeCIdentifier(lowerFirstChar(encField))
encTypeString = "UA_" + subv[0].__class__.__name__
instanceNameSafe = makeCIdentifier(instanceName)
code.append("UA_STACKARRAY(" + encTypeString + ", " + instanceNameSafe + "_" + memberName+f", {len(subv)});")
encTypeArr = nodeset.getDataTypeNode(subv[0].__class__.__name__).typesArray
encTypeArrayString = encTypeArr + "[" + encTypeArr + "_" + subv[0].__class__.__name__.upper() + "]"
code.append("UA_init({instanceName}, &{typeArrayString});".format(instanceName=instanceNameSafe + "_" + memberName,
typeArrayString=encTypeArrayString))
for subArrayIdx,val in enumerate(subv):
code.append(generateNodeValueCode(instanceNameSafe + "_" + memberName + "[" + str(subArrayIdx) + "]",
val, instanceName,instanceName + "_gehtNed_member", global_var_code, asIndirect=False))
code.append(instanceName + accessor + memberName + f"Size = {len(subv)};")
code.append(instanceName + accessor + memberName + " = " + instanceNameSafe+"_"+ memberName+";")
continue
logger.debug("Encoding of field " + memberName + " is " + str(subv.encodingRule) + "defined by " + str(encField))
if not subv.isNone():
# Some values can be optional
valueName = instanceName + accessor + memberName
code.append(generateNodeValueCode(valueName,
subv, instanceName,valueName, global_var_code, asIndirect=False, nodeset=nodeset, encRule=encRule))
if not isArrayElement:
code.append("UA_Variant_setScalar(&attr.value, " + instanceName + ", &" + typeArrayString + ");")
return [code, codeCleanup]
def getTypeBrowseName(dataTypeNode):
typeBrowseName = makeCIdentifier(dataTypeNode.browseName.name)
#TODO: review this
if typeBrowseName == "NumericRange":
# in the stack we define a separate structure for the numeric range, but
# the value itself is just a string
typeBrowseName = "String"
return typeBrowseName
def getTypesArrayForValue(nodeset, value):
typeNode = nodeset.getNodeByBrowseName(value.__class__.__name__)
if typeNode is None or value.isInternal:
typesArray = "UA_TYPES"
else:
typesArray = typeNode.typesArray
typeName = makeCIdentifier(value.__class__.__name__.upper())
return "&" + typesArray + "[" + typesArray + "_" + typeName + "]"
def isArrayVariableNode(node, parentNode):
return parentNode.valueRank is not None and (parentNode.valueRank != -1 and (parentNode.valueRank >= 0
or (len(node.value) > 1
and (parentNode.valueRank != -2 or parentNode.valueRank != -3))))
def generateValueCode(node, parentNode, nodeset, bootstrapping=True):
code = []
codeCleanup = []
codeGlobal = []
valueName = generateNodeIdPrintable(parentNode) + "_variant_DataContents"
# node.value either contains a list of multiple identical BUILTINTYPES, or it
# contains a single builtintype (which may be a container); choose if we need
# to create an array or a single variable.
# Note that some genious defined that there are arrays of size 1, which are
# distinctly different then a single value, so we need to check that as well
# Semantics:
# -3: Scalar or 1-dim
# -2: Scalar or x-dim | x>0
# -1: Scalar
# 0: x-dim | x>0
# n: n-dim | n>0
if (len(node.value) == 0):
return ["", "", ""]
if not isinstance(node.value[0], Value):
return ["", "", ""]
dataTypeNode = nodeset.getDataTypeNode(parentNode.dataType)
if isArrayVariableNode(node, parentNode):
# User the following strategy for all directly mappable values a la 'UA_Type MyInt = (UA_Type) 23;'
if isinstance(node.value[0], DiagnosticInfo):
logger.warn("Don't know how to print array of DiagnosticInfo in node " + str(parentNode.id))
elif isinstance(node.value[0], StatusCode):
logger.warn("Don't know how to print array of StatusCode in node " + str(parentNode.id))
else:
if isinstance(node.value[0], ExtensionObject):
code.append("UA_" + getTypeBrowseName(dataTypeNode) + " " + valueName + "[" + str(len(node.value)) + "];")
for idx, v in enumerate(node.value):
logger.debug("Building extObj array index " + str(idx))
instanceName = valueName + "[" + str(idx) + "]"
[code1, codeCleanup1] = generateExtensionObjectSubtypeCode(v, parent=parentNode, nodeset=nodeset,
global_var_code=codeGlobal, instanceName=instanceName,
isArrayElement=True)
code = code + code1
codeCleanup = codeCleanup + codeCleanup1
else:
code.append("UA_" + node.value[0].__class__.__name__ + " " + valueName + "[" + str(len(node.value)) + "];")
for idx, v in enumerate(node.value):
instanceName = generateNodeValueInstanceName(v, parentNode, idx)
code.append(generateNodeValueCode(
valueName + "[" + str(idx) + "]" , v, instanceName, valueName, codeGlobal))
code.append("UA_Variant_setArray(&attr.value, &" + valueName +
", (UA_Int32) " + str(len(node.value)) + ", " + "&" +
dataTypeNode.typesArray + "["+dataTypeNode.typesArray + "_" + getTypeBrowseName(dataTypeNode).upper() +"]);")
#scalar value
else:
# User the following strategy for all directly mappable values a la 'UA_Type MyInt = (UA_Type) 23;'
if isinstance(node.value[0], DiagnosticInfo):
logger.warn("Don't know how to print scalar DiagnosticInfo in node " + str(parentNode.id))
elif isinstance(node.value[0], StatusCode):
logger.warn("Don't know how to print scalar StatusCode in node " + str(parentNode.id))
else:
# The following strategy applies to all other types, in particular strings and numerics.
if isinstance(node.value[0], ExtensionObject):
[code1, codeCleanup1] = generateExtensionObjectSubtypeCode(node.value[0], parent=parentNode, nodeset=nodeset,
global_var_code=codeGlobal, isArrayElement=False)
code = code + code1
codeCleanup = codeCleanup + codeCleanup1
instanceName = generateNodeValueInstanceName(node.value[0], parentNode, 0)
if not node.value[0].isNone() and not(isinstance(node.value[0], ExtensionObject)):
code.append("UA_" + node.value[0].__class__.__name__ + " *" + valueName + " = UA_" + node.value[
0].__class__.__name__ + "_new();")
code.append("if (!" + valueName + ") return UA_STATUSCODE_BADOUTOFMEMORY;")
code.append("UA_" + node.value[0].__class__.__name__ + "_init(" + valueName + ");")
code.append(generateNodeValueCode("*" + valueName, node.value[0], instanceName, valueName, codeGlobal, asIndirect=True))
code.append(
"UA_Variant_setScalar(&attr.value, " + valueName + ", " +
getTypesArrayForValue(nodeset, node.value[0]) + ");")
if node.value[0].__class__.__name__ == "ByteString":
# The data is on the stack, not heap, so we can not delete the ByteString
codeCleanup.append(f"{valueName}->data = NULL;")
codeCleanup.append(f"{valueName}->length = 0;")
codeCleanup.append("UA_{}_delete({});".format(
node.value[0].__class__.__name__, valueName))
return [code, codeCleanup, codeGlobal]
def generateMethodNodeCode(node):
code = []
code.append("UA_MethodAttributes attr = UA_MethodAttributes_default;")
@ -469,13 +335,6 @@ def generateViewNodeCode(node):
code.append("attr.eventNotifier = (UA_Byte)%s;" % str(node.eventNotifier))
return code
def generateSubtypeOfDefinitionCode(node):
for ref in node.inverseReferences:
# 45 = HasSubtype
if ref.referenceType.i == 45:
return generateNodeIdCode(ref.target)
return "UA_NODEID_NULL"
def generateNodeCode_begin(node, nodeset, code_global):
code = []
codeCleanup = []

View File

@ -5,7 +5,7 @@
### file, You can obtain one at http://mozilla.org/MPL/2.0/.
### Copyright 2014-2015 (c) TU-Dresden (Author: Chris Iatrou)
### Copyright 2014-2017 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
### Copyright 2014-2017, 2025 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
### Copyright 2016-2017 (c) Stefan Profanter, fortiss GmbH
import logging
@ -27,380 +27,6 @@ logger = logging.getLogger(__name__)
namespaceMapping = {}
def getNextElementNode(xmlvalue):
if xmlvalue is None:
return None
xmlvalue = xmlvalue.nextSibling
while xmlvalue is not None and not xmlvalue.nodeType == xmlvalue.ELEMENT_NODE:
xmlvalue = xmlvalue.nextSibling
return xmlvalue
def valueIsInternalType(valueTypeString):
return valueTypeString.lower() in ['boolean', 'number', 'int32', 'uint32', 'int16', 'uint16',
'int64', 'uint64', 'byte', 'sbyte', 'float', 'double',
'string', 'bytestring', 'localizedtext', 'statuscode',
'diagnosticinfo', 'nodeid', 'guid', 'datetime',
'qualifiedname', 'expandednodeid', 'xmlelement', 'integer', 'uinteger']
class Value:
def __init__(self):
self.value = None
self.alias = None
self.dataType = None
self.encodingRule = []
self.isInternal = False
self.valueRank = None
def getValueFieldByAlias(self, fieldname):
if not isinstance(self.value, list):
return None
if not isinstance(self.value[0], Value):
return None
for val in self.value:
if val.alias() == fieldname:
return val.value
return None
def getTypeByString(self, stringName, encodingRule):
stringName = str(stringName.lower())
if stringName == 'boolean':
t = Boolean()
elif stringName == 'number':
t = Number()
elif stringName == 'integer':
t = Integer()
elif stringName == 'uinteger':
t = UInteger()
elif stringName == 'int32':
t = Int32()
elif stringName == 'uint32':
t = UInt32()
elif stringName == 'int16':
t = Int16()
elif stringName == 'uint16':
t = UInt16()
elif stringName == 'int64':
t = Int64()
elif stringName == 'uint64':
t = UInt64()
elif stringName == 'byte':
t = Byte()
elif stringName == 'sbyte':
t = SByte()
elif stringName == 'float':
t = Float()
elif stringName == 'double':
t = Double()
elif stringName == 'string':
t = String()
elif stringName == 'bytestring':
t = ByteString()
elif stringName == 'localizedtext':
t = LocalizedText()
elif stringName == 'statuscode':
t = StatusCode()
elif stringName == 'diagnosticinfo':
t = DiagnosticInfo()
elif stringName == 'nodeid':
t = NodeId()
elif stringName == 'guid':
t = Guid()
elif stringName == 'datetime':
t = DateTime()
elif stringName == 'qualifiedname':
t = QualifiedName()
elif stringName == 'expandednodeid':
t = ExpandedNodeId()
elif stringName == 'xmlelement':
t = XmlElement()
else:
logger.debug("No class representing stringName " + stringName + " was found. Cannot create builtinType.")
return None
t.encodingRule = encodingRule
return t
def checkXML(self, xmlvalue):
if xmlvalue is None or xmlvalue.nodeType != xmlvalue.ELEMENT_NODE:
logger.error("Expected XML Element, but got junk...")
return
def parseXMLEncoding(self, xmlvalue, parentDataTypeNode, parent, parser):
global namespaceMapping
self.checkXML(xmlvalue)
if "value" not in xmlvalue.localName.lower():
logger.error("Expected <Value> , but found " + xmlvalue.localName + \
" instead. Value will not be parsed.")
return
if len(xmlvalue.childNodes) == 0:
logger.error("Expected childnodes for value, but none were found...")
return
for n in xmlvalue.childNodes:
if n.nodeType == n.ELEMENT_NODE:
xmlvalue = n
break
namespaceMapping = parent.namespaceMapping
if "ListOf" in xmlvalue.localName:
self.value = []
for el in xmlvalue.childNodes:
if not el.nodeType == el.ELEMENT_NODE:
continue
val = self.__parseXMLSingleValue(el, parentDataTypeNode, parent, parser)
if val is None:
self.value = []
namespaceMapping = {}
return
self.value.append(val)
else:
self.value = [self.__parseXMLSingleValue(xmlvalue, parentDataTypeNode, parent, parser)]
namespaceMapping = {}
def __parseXMLSingleValue(self, xmlvalue, parentDataTypeNode, parent, parser, alias=None, encodingPart=None, valueRank=None):
enc = None
if encodingPart is None:
if parentDataTypeNode.symbolicName is not None:
for _, e in parser.types.items():
if enc is not None:
break
for key, value in e.items():
# Inside the parser are the symbolic names of the data types. If the display name and symbolic name are different, both must be checked.
# An example is the 3DFrame datatype where the symbolic name is ThreeDFrame.
if key == parentDataTypeNode.displayName.text or key == parentDataTypeNode.symbolicName.value:
enc = value
break
else:
for _, e in parser.types.items():
if enc is not None:
break
for key, value in e.items():
if key == parentDataTypeNode.displayName.text:
enc = value
break
else:
enc = encodingPart
ebodypart = xmlvalue
if xmlvalue.localName == "ExtensionObject":
extobj = ExtensionObject()
etype = xmlvalue.getElementsByTagName("TypeId")
if len(etype) == 0:
logger.error(str(parent.id) + ": Did not find <TypeId> for ExtensionObject")
return extobj
etype = etype[0].getElementsByTagName("Identifier")
if len(etype) == 0:
logger.error(str(parent.id) + ": Did not find <Identifier> for ExtensionObject")
return extobj
etype = NodeId(etype[0].firstChild.data.strip(' \t\n\r'))
extobj.typeId = etype
ebody = xmlvalue.getElementsByTagName("Body")
if len(ebody) == 0:
logger.error(str(parent.id) + ": Did not find <Body> for ExtensionObject")
return extobj
ebody = ebody[0]
try:
# Body must contain an Object of type 'DataType' as defined in Variable
ebodypart = ebody.firstChild
if not ebodypart.nodeType == ebodypart.ELEMENT_NODE:
ebodypart = getNextElementNode(ebodypart)
if ebodypart is None:
logger.error(str(parent.id) + ": Expected ExtensionObject to hold a variable of type " + str(parentDataTypeNode.browseName) + " but found nothing.")
return extobj
parentName = parentDataTypeNode.browseName.name
if parentDataTypeNode.symbolicName is not None and parentDataTypeNode.symbolicName.value is not None:
parentName = parentDataTypeNode.symbolicName.value
if not ebodypart.localName == "OptionSet" and not ebodypart.localName == parentName:
logger.error( str(parent.id) + ": Expected ExtensionObject to hold a variable of type " + str(parentDataTypeNode.browseName) + " but found " +
str(ebodypart.localName) + " instead.")
return extobj
extobj.alias = ebodypart.localName
ebodypart = ebodypart.firstChild
if not ebodypart.nodeType == ebodypart.ELEMENT_NODE:
ebodypart = getNextElementNode(ebodypart)
if ebodypart is None:
logger.error(str(parent.id) + ": Description of dataType " + str(parentDataTypeNode.browseName) + " in ExtensionObject is empty/invalid.")
return extobj
extobj.value = []
members = enc.members
# The EncodingMask must be skipped.
if ebodypart.localName == "EncodingMask":
ebodypart = getNextElementNode(ebodypart)
# No optional fields are set.
if ebodypart is None:
members = []
# The SwitchField must be checked. ebodypart could be None if only optional fields are included
# in the ExtensionObject and none of them is set.
if ebodypart is not None:
if ebodypart.localName == "SwitchField":
# The switch field is the index of the available union fields starting with 1
data = int(ebodypart.firstChild.data)
if data == 0:
# If the switch field is 0 then no field is present. A Union with no fields present has the same meaning as a NULL value.
members = []
else:
members = []
members.append(enc.members[data-1])
ebodypart = getNextElementNode(ebodypart)
for e in members:
# ebodypart can be None if the field is not set, although the field is not optional.
if ebodypart is None:
if not e.is_optional:
t = self.getTypeByString(e.member_type.name, None)
extobj.value.append(t)
extobj.encodingRule.append(e)
continue
if isinstance(e, StructMember):
if not e.name.lower() == ebodypart.localName.lower():
continue
extobj.encodingRule.append(e)
if isinstance(e.member_type, BuiltinType):
if e.is_array:
values = []
for el in ebodypart.childNodes:
if not el.nodeType == el.ELEMENT_NODE:
continue
t = self.getTypeByString(e.member_type.name, None)
t.parseXML(el)
values.append(t)
extobj.value.append(values)
else:
t = self.getTypeByString(e.member_type.name, None)
if t is not None: # cannot get Type for 'Variant' now
t.alias = ebodypart.localName
t.parseXML(ebodypart)
extobj.value.append(t)
elif isinstance(e.member_type, StructType):
# information is_array!
structure = Structure()
structure.alias = ebodypart.localName
structure.value = []
if e.is_array:
values = []
for el in ebodypart.childNodes:
if not el.nodeType == el.ELEMENT_NODE:
continue
structure.__parseXMLSingleValue(el, parentDataTypeNode, parent, parser, alias=None, encodingPart=e.member_type)
values.append(structure.value)
structure.value = []
structure.value = values
else:
structure.__parseXMLSingleValue(ebodypart, parentDataTypeNode, parent, parser, alias=None, encodingPart=e.member_type)
extobj.value.append(structure)
elif isinstance(e.member_type, EnumerationType):
t = self.getTypeByString("Int32", None)
t.parseXML(ebodypart)
extobj.value.append(t)
else:
logger.error(str(parent.id) + ": Description of dataType " + str(parentDataTypeNode.browseName) + " in ExtensionObject is not a BuildinType, StructType or EnumerationType.")
return extobj
else:
logger.error(str(parent.id) + ": Description of dataType " + str(parentDataTypeNode.browseName) + " in ExtensionObject is not a StructMember.")
return extobj
ebodypart = getNextElementNode(ebodypart)
except Exception as ex:
logger.error(str(parent.id) + f": Could not parse <Body> for ExtensionObject. {ex}")
elif valueIsInternalType(xmlvalue.localName):
t = self.getTypeByString(xmlvalue.localName, None)
t.parseXML(xmlvalue)
t.isInternal = True
return t
elif isinstance(enc, StructType):
members = enc.members
# The StructType can be a union and must be handled.
if enc.is_union:
body = xmlvalue.getElementsByTagName("SwitchField")
body = body[0]
# The switch field is the index of the available union fields starting with 1
if body.localName == "SwitchField":
data = int(body.firstChild.data)
if data == 0:
# If the switch field is 0 then no field is present. A Union with no fields present has the same meaning as a NULL value.
return None
else:
members = []
members.append(enc.members[data-1])
ebodypart = getNextElementNode(body)
else:
logger.error(str(parent.id) + ": Could not parse <SwitchFiled> for Union.")
return self
childValue = ebodypart.firstChild
if not childValue.nodeType == ebodypart.ELEMENT_NODE:
childValue = getNextElementNode(childValue)
for e in members:
if isinstance(e, StructMember):
self.encodingRule.append(e)
if isinstance(e.member_type, BuiltinType):
if e.is_array:
values = []
for el in childValue.childNodes:
if not el.nodeType == el.ELEMENT_NODE:
continue
t = self.getTypeByString(e.member_type.name, None)
t.parseXML(el)
values.append(t)
self.value.append(values)
else:
t = self.getTypeByString(e.member_type.name, None)
t.alias = e.name
if childValue is not None:
t.parseXML(childValue)
self.value.append(t)
else:
if not e.is_optional:
self.value.append(t)
elif isinstance(e.member_type, StructType):
structure = Structure()
structure.alias = e.name
structure.value = []
if not len(childValue.childNodes) == 0:
structure.__parseXMLSingleValue(childValue, parentDataTypeNode, parent, parser, alias=None, encodingPart=e.member_type)
self.value.append(structure)
return structure
elif isinstance(e.member_type, EnumerationType):
t = self.getTypeByString("Int32", None)
t.parseXML(childValue)
t.alias = e.name
self.value.append(t)
else:
logger.error(str(parent.id) + ": Description of dataType " + str(parentDataTypeNode.browseName) + " in ExtensionObject is not a BuildinType, EnumerationType or StructMember.")
return self
childValue = getNextElementNode(childValue)
return self
return extobj
def __str__(self):
return self.__class__.__name__ + "(" + str(self.value) + ")"
def isNone(self):
return self.value is None
def __repr__(self):
return self.__str__()
#################
# Builtin Types #
#################
def getXmlTextTrimmed(xmlNode):
if xmlNode is None or xmlNode.data is None:
return None
@ -410,208 +36,12 @@ def getXmlTextTrimmed(xmlNode):
return None
return content.strip()
#################
# Builtin Types #
#################
class Boolean(Value):
def __init__(self, xmlelement=None):
Value.__init__(self)
if xmlelement:
self.parseXML(xmlelement)
def parseXML(self, xmlvalue):
# Expect <Boolean>value</Boolean> or
# <Aliasname>value</Aliasname>
self.checkXML(xmlvalue)
val = getXmlTextTrimmed(xmlvalue.firstChild)
if val is None:
self.value = "false" # Catch XML <Boolean /> by setting the value to a default
else:
if "false" in xmlvalue.firstChild.data.lower():
self.value = "false"
else:
self.value = "true"
class Number(Value):
def __init__(self, xmlelement=None):
Value.__init__(self)
if xmlelement:
self.parseXML(xmlelement)
def parseXML(self, xmlvalue):
# Expect <Int16>value</Int16> or any other valid number type, or
# <Aliasname>value</Aliasname>
self.checkXML(xmlvalue)
val = getXmlTextTrimmed(xmlvalue.firstChild)
self.value = val if val is not None else 0
class Integer(Number):
def __init__(self, xmlelement=None):
Number.__init__(self)
if xmlelement:
self.parseXML(xmlelement)
class UInteger(Number):
def __init__(self, xmlelement=None):
Number.__init__(self)
if xmlelement:
self.parseXML(xmlelement)
class Byte(UInteger):
def __init__(self, xmlelement=None):
UInteger.__init__(self)
if xmlelement:
self.parseXML(xmlelement)
class SByte(Integer):
def __init__(self, xmlelement=None):
Integer.__init__(self)
if xmlelement:
self.parseXML(xmlelement)
class Int16(Integer):
def __init__(self, xmlelement=None):
Integer.__init__(self)
if xmlelement:
self.parseXML(xmlelement)
class UInt16(UInteger):
def __init__(self, xmlelement=None):
UInteger.__init__(self)
if xmlelement:
self.parseXML(xmlelement)
class Int32(Integer):
def __init__(self, xmlelement=None):
Integer.__init__(self)
if xmlelement:
self.parseXML(xmlelement)
def parseXML(self, xmlvalue):
Integer.parseXML(self, xmlvalue)
# Values of enumerations can be encoded as strings: <symbol>_<value> (see OPC specification part 6)
# UaModeler does this for enums that are fields of structs
# Extract <value> from string if possible
if isinstance(self.value, str) and not self.__strIsInt(self.value):
split = self.value.split('_')
if self.__strIsInt(split[len(split)-1]):
self.value = split[len(split)-1]
@staticmethod
def __strIsInt(strValue):
# 0_0 is not a valid number, but does not throw an error when converted to an integer.
# Therefore, this case must be checked separately.
try:
int(strValue)
if strValue[0:2] == '0_':
return False
return True
except:
return False
class UInt32(UInteger):
def __init__(self, xmlelement=None):
UInteger.__init__(self)
if xmlelement:
self.parseXML(xmlelement)
class Int64(Integer):
def __init__(self, xmlelement=None):
Integer.__init__(self)
if xmlelement:
self.parseXML(xmlelement)
class UInt64(UInteger):
def __init__(self, xmlelement=None):
UInteger.__init__(self)
if xmlelement:
self.parseXML(xmlelement)
class Float(Number):
def __init__(self, xmlelement=None):
Number.__init__(self)
if xmlelement:
self.parseXML(xmlelement)
def parseXML(self, xmlvalue):
# Expect <Float>value</Float> or
# <Aliasname>value</Aliasname>
self.checkXML(xmlvalue)
val = getXmlTextTrimmed(xmlvalue.firstChild)
self.value = val if val is not None else 0.0
class Double(Float):
def __init__(self, xmlelement=None):
Float.__init__(self)
if xmlelement:
self.parseXML(xmlelement)
class String(Value):
def __init__(self, xmlelement=None):
Value.__init__(self)
if xmlelement:
self.parseXML(xmlelement)
def pack(self):
bin = structpack("I", len(self.value))
bin = bin + str(self.value)
return bin
def parseXML(self, xmlvalue):
# Expect <String>value</String> or
# <Aliasname>value</Aliasname>
if not isinstance(xmlvalue, dom.Element):
self.value = xmlvalue
return
self.checkXML(xmlvalue)
val = getXmlTextTrimmed(xmlvalue.firstChild)
self.value = val if val is not None else ""
class XmlElement(String):
def __init__(self, xmlelement=None):
String.__init__(self, xmlelement)
class ByteString(Value):
def __init__(self, xmlelement=None):
Value.__init__(self)
def parseXML(self, xmlvalue):
# Expect <ByteString>value</ByteString>
if not isinstance(xmlvalue, dom.Element):
self.value = xmlvalue
return
self.checkXML(xmlvalue)
if xmlvalue.firstChild is None:
self.value = [] # Catch XML <ByteString /> by setting the value to a default
else:
self.value = b64decode(xmlvalue.firstChild.data)
class ExtensionObject(Value):
def __init__(self, xmlelement=None):
Value.__init__(self)
if xmlelement:
self.parseXML(xmlelement)
def parseXML(self, xmlelement):
pass
def __str__(self):
return "'ExtensionObject'"
class Structure(Value):
def __init__(self, xmlelement=None):
Value.__init__(self)
if xmlelement:
self.parseXML(xmlelement)
def parseXML(self, xmlelement):
pass
def __str__(self):
return "'Structure'"
class LocalizedText(Value):
class LocalizedText():
def __init__(self, xmlvalue=None):
Value.__init__(self)
self.locale = None
self.text = None
if xmlvalue:
@ -644,9 +74,8 @@ class LocalizedText(Value):
def isNone(self):
return self.text is None
class NodeId(Value):
class NodeId():
def __init__(self, idstring=None):
Value.__init__(self)
self.i = None
self.b = None
self.g = None
@ -745,53 +174,8 @@ class NodeId(Value):
def __hash__(self):
return hash(str(self))
class ExpandedNodeId(Value):
class QualifiedName():
def __init__(self, xmlelement=None):
Value.__init__(self)
if xmlelement:
self.parseXML(xmlelement)
def parseXML(self, xmlvalue):
self.checkXML(xmlvalue)
logger.debug("Not implemented", LOG_LEVEL_ERR)
class DateTime(Value):
def __init__(self, xmlelement=None):
Value.__init__(self)
if xmlelement:
self.parseXML(xmlelement)
def parseXML(self, xmlvalue):
# Expect <DateTime> or <AliasName>
# 2013-08-13T21:00:05.0000L
# </DateTime> or </AliasName>
self.checkXML(xmlvalue)
timestr = getXmlTextTrimmed(xmlvalue.firstChild)
if timestr is None:
# Catch XML <DateTime /> by setting the value to a default
self.value = datetime(2001, 1, 1)
else:
# .NET tends to create this garbage %Y-%m-%dT%H:%M:%S.0000z
# strip everything after the "." away for a posix time_struct
if "." in timestr:
timestr = timestr[:timestr.index(".")]
# If the last character is not numeric, remove it
while len(timestr) > 0 and timestr[-1] not in "0123456789":
timestr = timestr[:-1]
try:
self.value = datetime.strptime(timestr, "%Y-%m-%dT%H:%M:%S")
except Exception:
try:
self.value = datetime.strptime(timestr, "%Y-%m-%d")
except Exception:
logger.error("Timestring format is illegible. Expected 2001-01-30T21:22:23 or 2001-01-30, but got " + \
timestr + " instead. Time will be defaultet to now()")
self.value = datetime(2001, 1, 1)
class QualifiedName(Value):
def __init__(self, xmlelement=None):
Value.__init__(self)
self.ns = 0
self.name = None
if xmlelement:
@ -824,56 +208,3 @@ class QualifiedName(Value):
def isNone(self):
return self.name is None
class StatusCode(UInt32):
def __init__(self, xmlelement=None):
UInt32.__init__(self, xmlelement)
class DiagnosticInfo(Value):
def __init__(self, xmlelement=None):
Value.__init__(self)
if xmlelement:
self.parseXML(xmlelement)
def parseXML(self, xmlvalue):
self.checkXML(xmlvalue)
logger.warn("Not implemented")
class Guid(Value):
def __init__(self, xmlelement=None):
Value.__init__(self)
if xmlelement:
self.parseXML(xmlelement)
def parseXML(self, xmlvalue):
self.checkXML(xmlvalue)
# Support GUID in format:
# <Guid>
# <String>01234567-89AB-CDEF-ABCD-0123456789AB</String>
# </Guid>
val = None # set to None before check
if len(xmlvalue.getElementsByTagName("String")) != 0:
val = getXmlTextTrimmed(xmlvalue.getElementsByTagName("String")[0].firstChild)
else:
val = getXmlTextTrimmed(xmlvalue.firstChild) # no 'String' like 'DataSetFieldId'
if val is None:
self.value = ['00000000', '0000', '0000', '0000', '000000000000'] # Catch XML <Guid /> by setting the value to a default
else:
self.value = val
self.value = self.value.replace("{", "")
self.value = self.value.replace("}", "")
self.value = self.value.split("-")
tmp = []
for g in self.value:
try:
tmp.append(int("0x" + g, 16))
except Exception:
logger.error("Invalid formatting of Guid. Expected {01234567-89AB-CDEF-ABCD-0123456789AB}, got " + \
xmlvalue.firstChild.data)
self.value = ['00000000', '0000', '0000', '0000', '000000000000']
if len(tmp) != 5:
logger.error("Invalid formatting of Guid. Expected {01234567-89AB-CDEF-ABCD-0123456789AB}, got " + \
xmlvalue.firstChild.data)
self.value = ['00000000', '0000', '0000', '0000', '000000000000']

View File

@ -10,7 +10,7 @@
import logging
from datatypes import QualifiedName, LocalizedText, NodeId, String, Value, valueIsInternalType
from datatypes import QualifiedName, LocalizedText, NodeId
__all__ = ['Reference', 'RefOrAlias', 'Node', 'ReferenceTypeNode',
'ObjectNode', 'VariableNode', 'VariableTypeNode',
@ -75,9 +75,6 @@ class Node:
def __repr__(self):
return str(self)
def sanitize(self):
pass
def parseXML(self, xmlelement):
for idname in ['NodeId', 'NodeID', 'nodeid']:
if xmlelement.hasAttribute(idname):
@ -97,7 +94,7 @@ class Node:
elif at == "EventNotifier":
self.eventNotifier = int(av)
elif at == "SymbolicName":
self.symbolicName = String(av)
self.symbolicName = av
for x in xmlelement.childNodes:
if x.nodeType != x.ELEMENT_NODE:
@ -229,7 +226,6 @@ class VariableNode(Node):
self.minimumSamplingInterval = 0.0
self.historizing = False
self.value = None
self.xmlValueDef = None
if xmlelement:
VariableNode.parseXML(self, xmlelement)
@ -255,7 +251,7 @@ class VariableNode(Node):
if x.nodeType != x.ELEMENT_NODE:
continue
if x.localName == "Value":
self.xmlValueDef = x
self.value = x
elif x.localName == "DataType":
self.dataType = RefOrAlias(av)
elif x.localName == "ValueRank":
@ -274,20 +270,6 @@ class VariableNode(Node):
elif x.localName == "Historizing":
self.historizing = "false" not in x.lower()
def allocateValue(self, nodeset):
dataTypeNode = nodeset.getDataTypeNode(self.dataType)
if dataTypeNode is None:
return False
if self.xmlValueDef is None:
#logger.warn("Variable " + self.browseName() + "/" + str(self.id()) + " is not initialized. No memory will be allocated.")
return False
self.value = Value()
self.value.parseXMLEncoding(self.xmlValueDef, dataTypeNode, self, nodeset.parser)
return True
class VariableTypeNode(VariableNode):
def __init__(self, xmlelement=None):
VariableNode.__init__(self)
@ -340,40 +322,9 @@ class ObjectTypeNode(Node):
self.isAbstract = "false" not in av.lower()
class DataTypeNode(Node):
""" DataTypeNode is a subtype of Node describing DataType nodes.
DataType contain definitions and structure information usable for Variables.
The format of this structure is determined by buildEncoding()
Two definition styles are distinguished in XML:
1) A DataType can be a structure of fields, each field having a name and a type.
The type must be either an encodable builtin node (ex. UInt32) or point to
another DataType node that inherits its encoding from a builtin type using
an inverse "hasSubtype" (hasSuperType) reference.
2) A DataType may be an enumeration, in which each field has a name and a numeric
value.
The definition is stored as an ordered list of tuples. Depending on which
definition style was used, the __definition__ will hold
1) A list of ("Fieldname", Node) tuples.
2) A list of ("Fieldname", int) tuples.
A DataType (and in consequence all Variables using it) shall be deemed not
encodable if any of its fields cannot be traced to an encodable builtin type.
A DataType shall be further deemed not encodable if it contains mixed structure/
enumaration definitions.
If encodable, the encoding can be retrieved using getEncoding().
"""
def __init__(self, xmlelement=None):
Node.__init__(self)
self.isAbstract = False
self.__xmlDefinition__ = None
self.__baseTypeEncoding__ = []
self.__encodable__ = None
self.__definition__ = []
self.__isEnum__ = False
self.__isOptionSet__ = False
if xmlelement:
DataTypeNode.parseXML(self, xmlelement)
@ -383,248 +334,6 @@ class DataTypeNode(Node):
if at == "IsAbstract":
self.isAbstract = "false" not in av.lower()
for x in xmlelement.childNodes:
if x.nodeType == x.ELEMENT_NODE:
if x.localName == "Definition":
self.__xmlDefinition__ = x
def isEncodable(self):
""" Will return True if buildEncoding() was able to determine which builtin
type corresponds to all fields of this DataType.
If no encoding has been build yet an exception will be thrown.
Make sure to call buildEncoding() first.
"""
if self.__encodable__ is None:
raise Exception("Encoding needs to be built first using buildEncoding()")
return self.__encodable__
def getEncoding(self):
""" If the dataType is encodable, getEncoding() returns a nested list
containing the encoding the structure definition for this type.
If no encoding has been build yet an exception will be thrown.
Make sure to call buildEncoding() first.
If buildEncoding() has failed, an empty list will be returned.
"""
if self.__encodable__ is None:
raise Exception("Encoding needs to be built first using buildEncoding()")
if not self.__encodable__:
return []
return self.__baseTypeEncoding__
def buildEncoding(self, nodeset, indent=0, force=False, namespaceMapping=None):
""" buildEncoding() determines the structure and aliases used for variables
of this DataType.
The function will parse the XML <Definition> of the dataType and extract
"Name"-"Type" tuples. If successful, buildEncoding will return a nested
list of the following format:
[['Alias1', ['Alias2', ['BuiltinType']]], [Alias2, ['BuiltinType']], ...]
Aliases are fieldnames defined by this DataType or DataTypes referenced. A
list such as ['DataPoint', ['Int32']] indicates that a value will encode
an Int32 with the alias 'DataPoint' such as <DataPoint>12827</DataPoint>.
Only the first Alias of a nested list is considered valid for the BuiltinType.
Single-Elemented lists are always BuiltinTypes. Every nested list must
converge in a builtin type to be encodable. buildEncoding will follow
the first type inheritance reference (hasSupertype) of the dataType if
necessary;
If instead to "DataType" a numeric "Value" attribute is encountered,
the DataType will be considered an enumeration and all Variables using
it will be encoded as Int32.
DataTypes can be either structures or enumeration - mixed definitions will
be unencodable.
Calls to getEncoding() will be iterative. buildEncoding() can be called
only once per dataType, with all following calls returning the predetermined
value. Use of the 'force=True' parameter will force the Definition to be
reparsed.
After parsing, __definition__ holds the field definition as a list. Note
that this might deviate from the encoding, especially if inheritance was
used.
"""
prefix = " " + "|" * indent + "+"
if force is True:
self.__encodable__ = None
if self.__encodable__ is not None and self.__encodable__:
if self.isEncodable():
logger.debug(prefix + str(self.__baseTypeEncoding__) + " (already analyzed)")
else:
logger.debug( prefix + str(self.__baseTypeEncoding__) + "(already analyzed, not encodable!)")
return self.__baseTypeEncoding__
self.__encodable__ = True
if indent==0:
logger.debug("Parsing DataType " + str(self.browseName) + " (" + str(self.id) + ")")
if valueIsInternalType(self.browseName.name):
self.__baseTypeEncoding__ = [self.browseName.name]
self.__encodable__ = True
logger.debug( prefix + str(self.browseName) + "*")
logger.debug("Encodable as: " + str(self.__baseTypeEncoding__))
logger.debug("")
return self.__baseTypeEncoding__
# Check if there is a supertype available
parentType = None
for ref in self.references:
if ref.isForward:
continue
# hasSubtype
if ref.referenceType.i == 45:
targetNode = nodeset.nodes[ref.target]
if targetNode is not None and isinstance(targetNode, DataTypeNode):
parentType = targetNode
break
if self.__xmlDefinition__ is None:
if parentType is not None:
logger.debug( prefix + "Attempting definition using supertype " + str(targetNode.browseName) + " for DataType " + " " + str(self.browseName))
subenc = targetNode.buildEncoding(nodeset=nodeset, indent=indent+1,
namespaceMapping=namespaceMapping)
if not targetNode.isEncodable():
self.__encodable__ = True
else:
self.__baseTypeEncoding__ = self.__baseTypeEncoding__ + [self.browseName.name, subenc, None , 'false']
if len(self.__baseTypeEncoding__) == 0:
logger.debug(prefix + "No viable definition for " + str(self.browseName) + " " + str(self.id) + " found.")
self.__encodable__ = True
if indent==0:
if not self.__encodable__:
logger.debug("Not encodable (partial): " + str(self.__baseTypeEncoding__))
else:
logger.debug("Encodable as: " + str(self.__baseTypeEncoding__))
logger.debug( "")
return self.__baseTypeEncoding__
isEnum = False
# An option set is at the same time also an enum, at least for the encoding below
isOptionSet = parentType is not None and parentType.id.ns == 0 and parentType.id.i==12755
# We need to store the definition as ordered data, but cannot use orderedDict
# for backward compatibility with Python 2.6 and 3.4
enumDict = []
typeDict = []
# An XML Definition is provided and will be parsed... now
for x in self.__xmlDefinition__.childNodes:
if x.nodeType == x.ELEMENT_NODE:
fname = ""
fdtype = ""
enumVal = ""
valueRank = None
#symbolicName = None
#arrayDimensions = None
isOptional = ""
for at,av in x.attributes.items():
if at == "DataType":
fdtype = str(av)
if fdtype in nodeset.aliases:
fdtype = nodeset.aliases[fdtype]
elif at == "Name":
fname = str(av)
elif at == "SymbolicName":
pass # ignore
#symbolicName = str(av)
elif at == "Value":
enumVal = int(av)
isEnum = True
elif at == "ValueRank":
valueRank = int(av)
elif at == "IsOptional":
isOptional = str(av)
elif at == "ArrayDimensions":
pass # ignore
#arrayDimensions = int(av)
elif at == "AllowSubTypes":
pass # ignore
else:
logger.warn("Unknown Field Attribute " + str(at))
# This can either be an enumeration OR a structure, not both.
# Figure out which of the dictionaries gets the newly read value pair
if isEnum:
# This is an enumeration
enumDict.append((fname, enumVal))
continue
else:
if fdtype == "":
# If no datatype given use base datatype
fdtype = "i=24"
# This might be a subtype... follow the node defined as datatype to find out
# what encoding to use
fdTypeNodeId = NodeId(fdtype)
if namespaceMapping is not None:
fdTypeNodeId.ns = namespaceMapping[fdTypeNodeId.ns]
if fdTypeNodeId not in nodeset.nodes:
raise Exception(f"Node {fdTypeNodeId} not found in nodeset")
dtnode = nodeset.nodes[fdTypeNodeId]
# The node in the datatype element was found. we inherit its encoding,
# but must still ensure that the dtnode is itself validly encodable
typeDict.append([fname, dtnode])
fdtype = str(dtnode.browseName.name)
logger.debug( prefix + fname + " : " + fdtype + " -> " + str(dtnode.id))
subenc = dtnode.buildEncoding(nodeset=nodeset, indent=indent+1,
namespaceMapping=namespaceMapping)
if isOptional:
self.__baseTypeEncoding__ = self.__baseTypeEncoding__ + [[fname, subenc, valueRank, 'true']]
else:
self.__baseTypeEncoding__ = self.__baseTypeEncoding__ + [[fname, subenc, valueRank, 'false']]
if not dtnode.isEncodable():
# If we inherit an encoding from an unencodable node, this node is
# also not encodable
self.__encodable__ = True
break
# If we used inheritance to determine an encoding without alias, there is a
# the possibility that lists got double-nested despite of only one element
# being encoded, such as [['Int32']] or [['alias',['int32']]]. Remove that
# enclosing list.
while len(self.__baseTypeEncoding__) == 1 and isinstance(self.__baseTypeEncoding__[0], list):
self.__baseTypeEncoding__ = self.__baseTypeEncoding__[0]
if isOptionSet is True:
self.__isOptionSet__ = True
subenc = parentType.buildEncoding(nodeset=nodeset, namespaceMapping=namespaceMapping)
if not parentType.isEncodable():
self.__encodable__ = True
else:
self.__baseTypeEncoding__ = self.__baseTypeEncoding__ + [self.browseName.name, subenc, None]
self.__definition__ = enumDict
return self.__baseTypeEncoding__
if isEnum is True:
self.__baseTypeEncoding__ = self.__baseTypeEncoding__ + ['Int32']
self.__definition__ = enumDict
self.__isEnum__ = True
logger.debug( prefix+"Int32* -> enumeration with dictionary " + str(enumDict) + " encodable " + str(self.__encodable__))
return self.__baseTypeEncoding__
if indent==0:
if not self.__encodable__:
logger.debug( "Not encodable (partial): " + str(self.__baseTypeEncoding__))
else:
logger.debug( "Encodable as: " + str(self.__baseTypeEncoding__))
self.__isEnum__ = False
logger.debug( "")
self.__definition__ = typeDict
return self.__baseTypeEncoding__
class ViewNode(Node):
def __init__(self, xmlelement=None):
Node.__init__(self)

View File

@ -12,7 +12,7 @@ import xml.dom.minidom as dom
import logging
import codecs
import re
from datatypes import NodeId, valueIsInternalType
from datatypes import NodeId
from nodes import *
from opaque_type_mapping import opaque_type_mapping
@ -110,10 +110,6 @@ class NodeSet:
self.namespaceMapping = {}
def sanitize(self):
for n in self.nodes.values():
if n.sanitize() is False:
raise Exception("Failed to sanitize node " + str(n))
# Sanitize reference consistency
for n in self.nodes.values():
for ref in n.references:
@ -247,7 +243,6 @@ class NodeSet:
raise Exception(self, self.originXML + " contains no or more then 1 nodeset")
nodeset = nodesets[0]
# Extract the modelUri
try:
modelTag = nodeset.getElementsByTagName("Models")[0].getElementsByTagName("Model")[0]
@ -256,7 +251,6 @@ class NodeSet:
# Ignore exception and try to use namespace array
modelUri = None
# Create the namespace mapping
orig_namespaces = extractNamespaces(xmlfile) # List of namespaces used in the xml file
if modelUri is None and len(orig_namespaces) > 1:
@ -295,34 +289,6 @@ class NodeSet:
self.nodes[node.id] = node
newnodes[node.id] = node
# Parse Datatypes in order to find out what the XML keyed values actually
# represent.
# Ex. <rpm>123</rpm> is not encodable
# only after parsing the datatypes, it is known that
# rpm is encoded as a double
for n in newnodes.values():
if isinstance(n, DataTypeNode):
n.buildEncoding(self, namespaceMapping=self.namespaceMapping)
def getBinaryEncodingIdForNode(self, nodeId):
"""
The node should have a 'HasEncoding' forward reference which points to the encoding ids.
These can be XML Encoding or Binary Encoding. Therefore we also need to check if the SymbolicName
of the target node is "DefaultBinary"
"""
node = self.nodes[nodeId]
for ref in node.references:
if ref.referenceType.ns == 0 and ref.referenceType.i == 38:
refNode = self.nodes[ref.target]
if refNode.symbolicName.value == "DefaultBinary":
return ref.target
raise Exception("No DefaultBinary encoding defined for node " + str(nodeId))
def allocateVariables(self):
for n in self.nodes.values():
if isinstance(n, VariableNode):
n.allocateValue(self)
def getBaseDataType(self, node):
if node is None:
return None

View File

@ -188,10 +188,6 @@ ns.sanitize()
# Generate the BSD file from the XML.
ns.generateParser(args.existing, args.infiles, args.bsdFile)
# Allocate/Parse the data values. In order to do this, we must have run
# buidEncodingRules.
ns.allocateVariables()
# Create inverse references
ns.addInverseReferences()

View File

@ -1,81 +0,0 @@
#!/usr/bin/env python3
### This Source Code Form is subject to the terms of the Mozilla Public
### License, v. 2.0. If a copy of the MPL was not distributed with this
### file, You can obtain one at http://mozilla.org/MPL/2.0/.
### Copyright 2014-2015 (c) TU-Dresden (Author: Chris Iatrou)
### Copyright 2014-2017 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
### Copyright 2016-2017 (c) Stefan Profanter, fortiss GmbH
import codecs
from nodeset import *
class testing:
def __init__(self):
self.ns = NodeSet()
logger.debug("Phase 1: Reading XML file nodessets")
self.ns.parseXML("Opc.Ua.NodeSet2.xml")
# self.ns.parseXML("Opc.Ua.NodeSet2.Part4.xml")
# self.ns.parseXML("Opc.Ua.NodeSet2.Part5.xml")
# self.ns.parseXML("Opc.Ua.SimulationNodeSet2.xml")
logger.debug("Phase 2: Linking address space references and datatypes")
self.ns.linkOpenPointers()
self.ns.sanitize()
logger.debug("Phase 3: Allocating variable value data")
self.ns.allocateVariables()
bin = self.ns.buildBinary()
f = codecs.open("binary.base64", "w+", encoding='utf-8')
f.write(bin.encode("base64"))
f.close()
allnodes = self.ns.nodes
ns = [self.ns.getRoot()]
i = 0
# print "Starting depth search on " + str(len(allnodes)) + " nodes starting
# with from " + str(ns)
while (len(ns) < len(allnodes)):
i = i + 1
tmp = []
print("Iteration: " + str(i))
for n in ns:
tmp.append(n)
for r in n.getReferences():
if r.target() not in tmp:
tmp.append(r.target())
print("...tmp, " + str(len(tmp)) + " nodes discovered")
ns = []
for n in tmp:
ns.append(n)
print("...done, " + str(len(ns)) + " nodes discovered")
class testing_open62541_header:
def __init__(self):
self.ns = opcua_ns("testing")
logger.debug("Phase 1: Reading XML file nodessets")
self.ns.parseXML("Opc.Ua.NodeSet2.xml")
# self.ns.parseXML("Opc.Ua.NodeSet2.Part4.xml")
# self.ns.parseXML("Opc.Ua.NodeSet2.Part5.xml")
# self.ns.parseXML("Opc.Ua.SimulationNodeSet2.xml")
logger.debug("Phase 2: Linking address space references and datatypes")
self.ns.linkOpenPointers()
self.ns.sanitize()
logger.debug("Phase 3: Calling C Printers")
code = self.ns.printOpen62541Header()
codeout = codecs.open("./open62541_nodeset.c", "w+", encoding='utf-8')
for line in code:
codeout.write(line + "\n")
codeout.close()
return
if __name__ == '__main__':
tst = testing_open62541_header()