mirror of
https://github.com/open62541/open62541.git
synced 2025-06-03 04:00:21 +00:00
refactor(nc): Use UA_decodeXml to get the value at runtime from the original XML
This commit is contained in:
parent
a57d09955b
commit
bce2e3b6f3
@ -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">
|
||||
|
@ -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}
|
||||
|
@ -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}")
|
||||
|
@ -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)
|
@ -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 = []
|
||||
|
@ -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']
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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()
|
Loading…
Reference in New Issue
Block a user