#!/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, 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 NodeId from nodes import ReferenceTypeNode, ObjectNode, VariableNode, VariableTypeNode, MethodNode, ObjectTypeNode, DataTypeNode, ViewNode 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'(? splitLength: ret += "\"" + tmp[:splitLength].replace('"', r'\"') + "\" " tmp = tmp[splitLength:] ret += "\"" + re.sub(r'(? 0: code.append("attr.arrayDimensionsSize = %d;" % node.valueRank) code.append(f"UA_UInt32 arrayDimensions[{node.valueRank}];") if len(node.arrayDimensions) == node.valueRank: for idx, v in enumerate(node.arrayDimensions): code.append(f"arrayDimensions[{idx}] = {int(str(v))};") else: for dim in range(0, node.valueRank): code.append(f"arrayDimensions[{dim}] = 0;") code.append("attr.arrayDimensions = &arrayDimensions[0];") if node.dataType is None: # Inherit the datatype from the HasTypeDefinition reference, as stated in the OPC UA Spec: # 6.4.2 # "Instances inherit the initial values for the Attributes that they have in common with the # TypeDefinitionNode from which they are instantiated, with the exceptions of the NodeClass and # NodeId." setNodeDatatypeRecursive(node, nodeset) code.append("/* DataType inherited */") 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)) code.append("attr.dataType = %s;" % generateNodeIdCode(node.dataType)) 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] def generateVariableNodeCode(node, nodeset): code = [] codeCleanup = [] codeGlobal = [] code.append("UA_VariableAttributes attr = UA_VariableAttributes_default;") if node.historizing: code.append("attr.historizing = true;") code.append("attr.minimumSamplingInterval = %f;" % node.minimumSamplingInterval) code.append("attr.userAccessLevel = %d;" % node.userAccessLevel) code.append("attr.accessLevel = %d;" % node.accessLevel) [code1, codeCleanup1, codeGlobal1] = generateCommonVariableCode(node, nodeset) code += code1 codeCleanup += codeCleanup1 codeGlobal += codeGlobal1 return [code, codeCleanup, codeGlobal] def generateVariableTypeNodeCode(node, nodeset): code = [] codeCleanup = [] codeGlobal = [] code.append("UA_VariableTypeAttributes attr = UA_VariableTypeAttributes_default;") if node.isAbstract: code.append("attr.isAbstract = true;") [code1, codeCleanup1, codeGlobal1] = generateCommonVariableCode(node, nodeset) code += code1 codeCleanup += codeCleanup1 codeGlobal += codeGlobal1 return [code, codeCleanup, codeGlobal] def generateMethodNodeCode(node): code = [] code.append("UA_MethodAttributes attr = UA_MethodAttributes_default;") if node.executable: code.append("attr.executable = true;") if node.userExecutable: code.append("attr.userExecutable = true;") return code def generateObjectTypeNodeCode(node): code = [] code.append("UA_ObjectTypeAttributes attr = UA_ObjectTypeAttributes_default;") if node.isAbstract: code.append("attr.isAbstract = true;") return code def generateDataTypeNodeCode(node): code = [] code.append("UA_DataTypeAttributes attr = UA_DataTypeAttributes_default;") if node.isAbstract: code.append("attr.isAbstract = true;") return code def generateViewNodeCode(node): code = [] code.append("UA_ViewAttributes attr = UA_ViewAttributes_default;") if node.containsNoLoops: code.append("attr.containsNoLoops = true;") code.append("attr.eventNotifier = (UA_Byte)%s;" % str(node.eventNotifier)) return code def generateNodeCode_begin(node, nodeset, code_global): code = [] codeCleanup = [] code.append("UA_StatusCode retVal = UA_STATUSCODE_GOOD;") # Attributes if isinstance(node, ReferenceTypeNode): code.extend(generateReferenceTypeNodeCode(node)) elif isinstance(node, ObjectNode): code.extend(generateObjectNodeCode(node)) elif isinstance(node, VariableNode) and not isinstance(node, VariableTypeNode): [code1, codeCleanup1, codeGlobal1] = generateVariableNodeCode(node, nodeset) code.extend(code1) codeCleanup.extend(codeCleanup1) code_global.extend(codeGlobal1) elif isinstance(node, VariableTypeNode): [code1, codeCleanup1, codeGlobal1] = generateVariableTypeNodeCode(node, nodeset) code.extend(code1) codeCleanup.extend(codeCleanup1) code_global.extend(codeGlobal1) elif isinstance(node, MethodNode): code.extend(generateMethodNodeCode(node)) elif isinstance(node, ObjectTypeNode): code.extend(generateObjectTypeNodeCode(node)) elif isinstance(node, DataTypeNode): code.extend(generateDataTypeNodeCode(node)) elif isinstance(node, ViewNode): code.extend(generateViewNodeCode(node)) if node.displayName is not None: code.append("attr.displayName = " + generateLocalizedTextCode(node.displayName, alloc=False) + ";") if node.description is not None: code.append("\n#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS\n") code.append("attr.description = " + generateLocalizedTextCode(node.description, alloc=False) + ";") code.append("\n#endif\n") if node.writeMask is not None: code.append("attr.writeMask = %d;" % node.writeMask) if node.userWriteMask is not None: code.append("attr.userWriteMask = %d;" % node.userWriteMask) # AddNodes call addnode = [] addnode.append("retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_{},". format(makeCIdentifier(node.__class__.__name__.upper().replace("NODE" ,"")))) addnode.append(generateNodeIdCode(node.id) + ",") addnode.append(generateNodeIdCode(node.parent.id if node.parent else NodeId()) + ",") addnode.append(generateNodeIdCode(node.parentReference.id if node.parent else NodeId()) + ",") addnode.append(generateQualifiedNameCode(node.browseName) + ",") if isinstance(node, VariableNode) or isinstance(node, ObjectNode): typeDefRef = node.popTypeDef() addnode.append(generateNodeIdCode(typeDefRef.target) + ",") else: addnode.append(" UA_NODEID_NULL,") addnode.append("(const UA_NodeAttributes*)&attr, &UA_TYPES[UA_TYPES_{}ATTRIBUTES],NULL, NULL);". format(makeCIdentifier(node.__class__.__name__.upper().replace("NODE" ,"")))) code.append("".join(addnode)) code.extend(codeCleanup) return "\n".join(code) def generateNodeCode_finish(node): code = [] if isinstance(node, MethodNode): code.append("UA_Server_addMethodNode_finish(server, ") code.append(generateNodeIdCode(node.id)) code.append(", NULL, 0, NULL, 0, NULL);") else: code.append("UA_Server_addNode_finish(server, ") code.append(generateNodeIdCode(node.id)) code.append(");") return "".join(code)