Parcourir la source

Fix node ordering and successfully generate code for full NS0

Stefan Profanter il y a 6 ans
Parent
commit
9d582a7d13

+ 8 - 10
CMakeLists.txt

@@ -103,7 +103,6 @@ mark_as_advanced(UA_ENABLE_NONSTANDARD_UDP)
 # Build Targets
 option(UA_BUILD_EXAMPLES "Build example servers and clients" OFF)
 option(UA_BUILD_UNIT_TESTS "Build the unit tests" OFF)
-option(UA_BUILD_DOCUMENTATION "Generate doxygen/sphinx documentation" OFF)
 option(UA_BUILD_FUZZING "Build the fuzzing executables" OFF)
 mark_as_advanced(UA_BUILD_FUZZING)
 if (UA_BUILD_FUZZING)
@@ -476,7 +475,8 @@ add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src_generated/ua_namespace0.c
                    PRE_BUILD
                    COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/tools/nodeset_compiler/nodeset_compiler.py
                            --generate-ns0
-                           ${UA_NAMESPACE0_XML}
+                           --ignore ${PROJECT_SOURCE_DIR}/tools/nodeset_compiler/NodeID_NS0_Base.txt
+                           --xml ${UA_NAMESPACE0_XML}
                            ${PROJECT_BINARY_DIR}/src_generated/ua_namespace0
                    DEPENDS ${UA_NAMESPACE0_XML}
                            ${PROJECT_SOURCE_DIR}/tools/nodeset_compiler/nodeset_compiler.py
@@ -488,8 +488,8 @@ add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src_generated/ua_namespace0.c
                            ${PROJECT_SOURCE_DIR}/tools/nodeset_compiler/backend_open62541_datatypes.py)
 # we need a custom target to avoid that the generator is called concurrently and thus overwriting files while the other thread is compiling
 add_custom_target(open62541-generator-namespace DEPENDS
-        ${PROJECT_BINARY_DIR}/src_generated/ua_namespaceinit_generated.c
-        ${PROJECT_BINARY_DIR}/src_generated/ua_namespaceinit_generated.h)
+        ${PROJECT_BINARY_DIR}/src_generated/ua_namespace0.c
+        ${PROJECT_BINARY_DIR}/src_generated/ua_namespace0.h)
 
 #####################
 # Build the Library #
@@ -545,12 +545,10 @@ else()
     endif()
 endif()
 
-if(UA_ENABLE_GENERATE_NAMESPACE0)
-    add_dependencies(open62541-amalgamation-source open62541-generator-namespace)
-    add_dependencies(open62541-amalgamation-header open62541-generator-namespace)
-    if(NOT UA_ENABLE_AMALGAMATION)
-        add_dependencies(open62541-object open62541-generator-namespace)
-    endif()
+add_dependencies(open62541-amalgamation-source open62541-generator-namespace)
+add_dependencies(open62541-amalgamation-header open62541-generator-namespace)
+if(NOT UA_ENABLE_AMALGAMATION)
+    add_dependencies(open62541-object open62541-generator-namespace)
 endif()
 
 # Export Symbols

+ 24 - 27
examples/CMakeLists.txt

@@ -78,32 +78,30 @@ if(UA_ENABLE_NODEMANAGEMENT)
     add_example(access_control_client access_control/client_access_control.c)
 endif()
 
-if(UA_BUILD_EXAMPLES_NODESET_COMPILER)
-  if(BUILD_SHARED_LIBS)
-    message(FATAL_ERROR "The nodeset compiler currently requires static linking to access internal API")
-  endif()
-
-  # example information model from nodeset xml
-  add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src_generated/nodeset.h ${PROJECT_BINARY_DIR}/src_generated/nodeset.c
-                  PRE_BUILD
-                  COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/tools/pyUANamespace/generate_open62541CCode.py
-                                                -i ${PROJECT_SOURCE_DIR}/tools/pyUANamespace/NodeID_Blacklist_FullNS0.txt
-						${PROJECT_SOURCE_DIR}/tools/schema/namespace0/Opc.Ua.NodeSet2.xml
-                                                ${PROJECT_SOURCE_DIR}/examples/server_nodeset.xml
-                                                ${PROJECT_BINARY_DIR}/src_generated/nodeset
-                  DEPENDS ${PROJECT_SOURCE_DIR}/tools/pyUANamespace/generate_open62541CCode.py
-                          ${PROJECT_SOURCE_DIR}/tools/pyUANamespace/open62541_MacroHelper.py
-                          ${PROJECT_SOURCE_DIR}/tools/pyUANamespace/ua_builtin_types.py
-                          ${PROJECT_SOURCE_DIR}/tools/pyUANamespace/ua_constants.py
-                          ${PROJECT_SOURCE_DIR}/tools/pyUANamespace/ua_namespace.py
-                          ${PROJECT_SOURCE_DIR}/tools/pyUANamespace/ua_node_types.py
-                          ${PROJECT_SOURCE_DIR}/tools/pyUANamespace/NodeID_Blacklist_FullNS0.txt
-                          ${PROJECT_SOURCE_DIR}/examples/server_nodeset.xml)
-
-  add_executable(server_nodeset server_nodeset.c ${PROJECT_BINARY_DIR}/src_generated/nodeset.c $<TARGET_OBJECTS:open62541-object>)
-  target_link_libraries(server_nodeset ${LIBS})
-  target_include_directories(server_nodeset PRIVATE ${PROJECT_SOURCE_DIR}/src ${PROJECT_SOURCE_DIR}/deps) # needs an internal header
-  set_target_properties(server_nodeset PROPERTIES COMPILE_FLAGS "-Wno-pedantic -Wno-sign-conversion")
+# generate nodeset from XML file
+add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src_generated/example_nodeset.c
+                          ${PROJECT_BINARY_DIR}/src_generated/example_nodeset.h
+                   PRE_BUILD
+                   COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/tools/nodeset_compiler/nodeset_compiler.py
+                           --types-array=UA_TYPES
+                           --existing ${UA_NAMESPACE0_XML}
+                           --xml ${PROJECT_SOURCE_DIR}/examples/server_nodeset.xml
+                           ${PROJECT_BINARY_DIR}/src_generated/example_nodeset
+                   DEPENDS ${UA_NAMESPACE0_XML}
+                           ${PROJECT_SOURCE_DIR}/tools/nodeset_compiler/nodeset_compiler.py
+                           ${PROJECT_SOURCE_DIR}/tools/nodeset_compiler/nodes.py
+                           ${PROJECT_SOURCE_DIR}/tools/nodeset_compiler/nodeset.py
+                           ${PROJECT_SOURCE_DIR}/tools/nodeset_compiler/datatypes.py
+                           ${PROJECT_SOURCE_DIR}/tools/nodeset_compiler/backend_open62541.py
+                           ${PROJECT_SOURCE_DIR}/tools/nodeset_compiler/backend_open62541_nodes.py
+                           ${PROJECT_SOURCE_DIR}/tools/nodeset_compiler/backend_open62541_datatypes.py
+                           ${PROJECT_SOURCE_DIR}/examples/server_nodeset.xml
+                   )
+
+include_directories(${PROJECT_SOURCE_DIR}/src)
+add_example(server_nodeset server_nodeset.c ${PROJECT_BINARY_DIR}/src_generated/example_nodeset.c)
+if(UA_COMPILE_AS_CXX)
+	set_source_files_properties(${PROJECT_BINARY_DIR}/src_generated/example_nodeset.c PROPERTIES LANGUAGE CXX)
 endif()
 
 if(UA_BUILD_SELFSIGNED_CERTIFICATE)
@@ -112,7 +110,6 @@ if(UA_BUILD_SELFSIGNED_CERTIFICATE)
                      COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/tools/certs/create_self-signed.py ${CMAKE_CURRENT_BINARY_DIR}
                      DEPENDS ${PROJECT_SOURCE_DIR}/tools/certs/create_self-signed.py
                              ${PROJECT_SOURCE_DIR}/tools/certs/localhost.cnf)
-                             
   add_custom_target(selfsigned ALL DEPENDS server_cert.der ca.crt)
   add_executable(server_certificate server_certificate.c ${STATIC_OBJECTS} server_cert.der ca.crt)
   target_link_libraries(server_certificate open62541 ${open62541_LIBRARIES})

+ 3 - 3
examples/server_nodeset.c

@@ -9,7 +9,7 @@
 
 /* Files nodeset.h and nodeset.c are created from server_nodeset.xml in the
  * /src_generated directory by CMake */
-#include "nodeset.h"
+#include "example_nodeset.h"
 
 UA_Boolean running = true;
 
@@ -27,13 +27,13 @@ int main(int argc, char** argv) {
     UA_Server *server = UA_Server_new(config);
 
     /* create nodes from nodeset */
-    if(nodeset(server) != UA_STATUSCODE_GOOD) {
+    if(example_nodeset(server) != UA_STATUSCODE_GOOD) {
         UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Namespace index for generated "
                      "nodeset does not match. The call to the generated method has to be "
                      "before any other namespace add calls.");
         UA_Server_delete(server);
         UA_ServerConfig_delete(config);
-        return UA_STATUSCODE_BADUNEXPECTEDERROR;
+        return (int)UA_STATUSCODE_BADUNEXPECTEDERROR;
     }
 
     UA_StatusCode retval = UA_Server_run(server, &running);

+ 1 - 1
src/server/ua_mdns.c

@@ -28,7 +28,7 @@
 #ifndef UA_STRDUP
 # if defined(__MINGW32__)
 static char *ua_strdup(const char *s) {
-    char *p = UA_malloc(strlen(s) + 1);
+    char *p = (char*)UA_malloc(strlen(s) + 1);
     if(p) { strcpy(p, s); }
     return p;
 }

+ 9 - 184
src/server/ua_server.c

@@ -151,184 +151,6 @@ UA_Server_cleanup(UA_Server *server, void *_) {
 #endif
 }
 
-static void initNamespace0(UA_Server *server) {
-    /* Load nodes and references generated from the XML ns0 definition */
-    server->bootstrapNS0 = true;
-    ua_namespace0(server);
-    server->bootstrapNS0 = false;
-
-    /* NamespaceArray */
-    UA_DataSource namespaceDataSource = {.handle = server, .read = readNamespaces, .write = NULL};
-    UA_Server_setVariableNode_dataSource(server,
-                                         UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_NAMESPACEARRAY), namespaceDataSource);
-
-    /* ServerArray */
-    writeNs0VariableArray(server, UA_NS0ID_SERVER_SERVERARRAY,
-                          &server->config.applicationDescription.applicationUri,
-                          1, &UA_TYPES[UA_TYPES_STRING]);
-
-    /* LocaleIdArray */
-    UA_String locale_en = UA_STRING("en");
-    writeNs0VariableArray(server, UA_NS0ID_SERVER_SERVERCAPABILITIES_LOCALEIDARRAY,
-                          &locale_en, 1, &UA_TYPES[UA_TYPES_STRING]);
-
-    /* MaxBrowseContinuationPoints */
-    UA_UInt16 maxBrowseContinuationPoints = MAXCONTINUATIONPOINTS;
-    writeNs0Variable(server, UA_NS0ID_SERVER_SERVERCAPABILITIES_MAXBROWSECONTINUATIONPOINTS,
-                     &maxBrowseContinuationPoints, &UA_TYPES[UA_TYPES_UINT16]);
-
-    /* ServerProfileArray */
-    UA_String profileArray[4];
-    UA_UInt16 profileArraySize = 0;
-#define ADDPROFILEARRAY(x) profileArray[profileArraySize++] = UA_STRING_ALLOC(x)
-    ADDPROFILEARRAY("http://opcfoundation.org/UA-Profile/Server/NanoEmbeddedDevice");
-#ifdef UA_ENABLE_NODEMANAGEMENT
-    ADDPROFILEARRAY("http://opcfoundation.org/UA-Profile/Server/NodeManagement");
-#endif
-#ifdef UA_ENABLE_METHODCALLS
-    ADDPROFILEARRAY("http://opcfoundation.org/UA-Profile/Server/Methods");
-#endif
-#ifdef UA_ENABLE_SUBSCRIPTIONS
-    ADDPROFILEARRAY("http://opcfoundation.org/UA-Profile/Server/EmbeddedDataChangeSubscription");
-#endif
-    writeNs0VariableArray(server, UA_NS0ID_SERVER_SERVERCAPABILITIES_SERVERPROFILEARRAY,
-                          profileArray, profileArraySize, &UA_TYPES[UA_TYPES_STRING]);
-
-    /* MaxQueryContinuationPoints */
-    UA_UInt16 maxQueryContinuationPoints = 0;
-    writeNs0Variable(server, UA_NS0ID_SERVER_SERVERCAPABILITIES_MAXQUERYCONTINUATIONPOINTS,
-                     &maxQueryContinuationPoints, &UA_TYPES[UA_TYPES_UINT16]);
-
-    /* MaxHistoryContinuationPoints */
-    UA_UInt16 maxHistoryContinuationPoints = 0;
-    writeNs0Variable(server, UA_NS0ID_SERVER_SERVERCAPABILITIES_MAXHISTORYCONTINUATIONPOINTS,
-                     &maxHistoryContinuationPoints, &UA_TYPES[UA_TYPES_UINT16]);
-
-    /* MinSupportedSampleRate */
-    writeNs0Variable(server, UA_NS0ID_SERVER_SERVERCAPABILITIES_MINSUPPORTEDSAMPLERATE,
-                     &server->config.samplingIntervalLimits.min, &UA_TYPES[UA_TYPES_UINT16]);
-
-    /* ServerDiagnostics - ServerDiagnosticsSummary */
-    UA_ServerDiagnosticsSummaryDataType serverDiagnosticsSummary;
-    UA_ServerDiagnosticsSummaryDataType_init(&serverDiagnosticsSummary);
-    writeNs0Variable(server, UA_NS0ID_SERVER_SERVERDIAGNOSTICS_SERVERDIAGNOSTICSSUMMARY,
-                     &serverDiagnosticsSummary, &UA_TYPES[UA_TYPES_SERVERDIAGNOSTICSSUMMARYDATATYPE]);
-
-    /* ServerDiagnostics - EnabledFlag */
-    UA_Boolean enabledFlag = false;
-    writeNs0Variable(server, UA_NS0ID_SERVER_SERVERDIAGNOSTICS_ENABLEDFLAG,
-                     &enabledFlag, &UA_TYPES[UA_TYPES_BOOLEAN]);
-
-    /* ServerStatus */
-    UA_DataSource serverStatus = {.handle = server, .read = readStatus, .write = NULL};
-    UA_Server_setVariableNode_dataSource(server,
-                                         UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS), serverStatus);
-
-    /* StartTime */
-    writeNs0Variable(server, UA_NS0ID_SERVER_SERVERSTATUS_STARTTIME,
-                     &server->startTime, &UA_TYPES[UA_TYPES_DATETIME]);
-
-    /* CurrentTime */
-    UA_DataSource currentTime = {.handle = server, .read = readCurrentTime, .write = NULL};
-    UA_Server_setVariableNode_dataSource(server,
-                                         UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS), currentTime);
-
-    /* State */
-    UA_ServerState state = UA_SERVERSTATE_RUNNING;
-    writeNs0Variable(server, UA_NS0ID_SERVER_SERVERSTATUS_STATE,
-                     &state, &UA_TYPES[UA_TYPES_SERVERSTATE]);
-
-    /* BuildInfo */
-    writeNs0Variable(server, UA_NS0ID_SERVER_SERVERSTATUS_BUILDINFO,
-                     &server->config.buildInfo, &UA_TYPES[UA_TYPES_BUILDINFO]);
-
-    /* BuildInfo - ProductUri */
-    writeNs0Variable(server, UA_NS0ID_SERVER_SERVERSTATUS_BUILDINFO_PRODUCTURI,
-                     &server->config.buildInfo.productUri, &UA_TYPES[UA_TYPES_STRING]);
-
-    /* BuildInfo - ManufacturerName */
-    writeNs0Variable(server, UA_NS0ID_SERVER_SERVERSTATUS_BUILDINFO_MANUFACTURERNAME,
-                     &server->config.buildInfo.manufacturerName, &UA_TYPES[UA_TYPES_STRING]);
-
-    /* BuildInfo - ProductName */
-    writeNs0Variable(server, UA_NS0ID_SERVER_SERVERSTATUS_BUILDINFO_PRODUCTNAME,
-                     &server->config.buildInfo.productName, &UA_TYPES[UA_TYPES_STRING]);
-
-    /* BuildInfo - SoftwareVersion */
-    writeNs0Variable(server, UA_NS0ID_SERVER_SERVERSTATUS_BUILDINFO_SOFTWAREVERSION,
-                     &server->config.buildInfo.softwareVersion, &UA_TYPES[UA_TYPES_STRING]);
-
-    /* BuildInfo - BuildNumber */
-    writeNs0Variable(server, UA_NS0ID_SERVER_SERVERSTATUS_BUILDINFO_BUILDNUMBER,
-                     &server->config.buildInfo.buildNumber, &UA_TYPES[UA_TYPES_STRING]);
-
-    /* BuildInfo - BuildDate */
-    writeNs0Variable(server, UA_NS0ID_SERVER_SERVERSTATUS_BUILDINFO_BUILDDATE,
-                     &server->config.buildInfo.buildDate, &UA_TYPES[UA_TYPES_DATETIME]);
-
-    /* SecondsTillShutdown */
-    UA_UInt32 secondsTillShutdown = 0;
-    writeNs0Variable(server, UA_NS0ID_SERVER_SERVERSTATUS_SECONDSTILLSHUTDOWN,
-                     &secondsTillShutdown, &UA_TYPES[UA_TYPES_UINT32]);
-
-    /* ShutDownReason */
-    UA_LocalizedText shutdownReason;
-    UA_LocalizedText_init(&shutdownReason);
-    writeNs0Variable(server, UA_NS0ID_SERVER_SERVERSTATUS_SHUTDOWNREASON,
-                     &shutdownReason, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]);
-
-    /* ServiceLevel */
-    UA_DataSource serviceLevel = {.handle = server, .read = readServiceLevel, .write = NULL};
-    UA_Server_setVariableNode_dataSource(server,
-                                         UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVICELEVEL), serviceLevel);
-
-    /* Auditing */
-    UA_DataSource auditing = {.handle = server, .read = readAuditing, .write = NULL};
-    UA_Server_setVariableNode_dataSource(server,
-                                         UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_AUDITING), auditing);
-
-    /* Redundancy Support */
-    /* TODO: Use enum */
-    UA_Int32 redundancySupport = 0;
-    writeNs0Variable(server, UA_NS0ID_SERVER_SERVERREDUNDANCY_REDUNDANCYSUPPORT,
-                     &redundancySupport, &UA_TYPES[UA_TYPES_INT32]);
-
-#if defined(UA_ENABLE_METHODCALLS) && defined(UA_ENABLE_SUBSCRIPTIONS)
-    UA_Argument inputArguments;
-    UA_Argument_init(&inputArguments);
-    inputArguments.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
-    inputArguments.name = UA_STRING("SubscriptionId");
-    inputArguments.valueRank = -1; /* scalar argument */
-
-    UA_Argument outputArguments[2];
-    UA_Argument_init(&outputArguments[0]);
-    outputArguments[0].dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
-    outputArguments[0].name = UA_STRING("ServerHandles");
-    outputArguments[0].valueRank = 1;
-
-    UA_Argument_init(&outputArguments[1]);
-    outputArguments[1].dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
-    outputArguments[1].name = UA_STRING("ClientHandles");
-    outputArguments[1].valueRank = 1;
-
-    UA_MethodAttributes addmethodattributes;
-    UA_MethodAttributes_init(&addmethodattributes);
-    addmethodattributes.displayName = UA_LOCALIZEDTEXT("", "GetMonitoredItems");
-    addmethodattributes.executable = true;
-    addmethodattributes.userExecutable = true;
-
-    UA_Server_addMethodNode(server, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_GETMONITOREDITEMS),
-                            UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER),
-                            UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
-                            UA_QUALIFIEDNAME(0, "GetMonitoredItems"), addmethodattributes,                   readMonitoredItems
-    , /* callback of the method node */
-                            NULL, /* handle passed with the callback */
-                            1, &inputArguments,
-                            2, outputArguments,
-                            NULL);
-#endif
-}
-
 /********************/
 /* Server Lifecycle */
 /********************/
@@ -377,11 +199,6 @@ UA_Server_new(const UA_ServerConfig *config) {
     /* Initialized SecureChannel and Session managers */
     UA_SecureChannelManager_init(&server->secureChannelManager, server);
     UA_SessionManager_init(&server->sessionManager, server);
-#ifdef UA_ENABLE_MULTITHREADING
-    rcu_init();
-    cds_wfcq_init(&server->dispatchQueue_head, &server->dispatchQueue_tail);
-    cds_lfs_init(&server->mainLoopJobs);
-#endif
 
     /* Add a regular callback for cleanup and maintenance */
     UA_Server_addRepeatedCallback(server, (UA_ServerCallback)UA_Server_cleanup, NULL,
@@ -416,7 +233,15 @@ UA_Server_new(const UA_ServerConfig *config) {
 #endif
 
     /* Initialize namespace 0*/
-    initNamespace0(server);
+    UA_StatusCode retVal = UA_Server_initNS0(server);
+    if (retVal != UA_STATUSCODE_GOOD) {
+        UA_LOG_ERROR(config->logger,
+                     UA_LOGCATEGORY_SERVER,
+                     "Initialization of Namespace 0 failed with %s. See previous outputs for any error messages.",
+                     UA_StatusCode_name(retVal));
+        UA_Server_delete(server);
+        return NULL;
+    }
 
     return server;
 }

+ 3 - 6
src/server/ua_server_internal.h

@@ -332,14 +332,13 @@ UA_Discovery_removeRecord(UA_Server *server, const UA_String *servername,
 UA_StatusCode
 Operation_addNode_begin(UA_Server *server, UA_Session *session,
                         const UA_AddNodesItem *item, void *nodeContext,
-                        UA_NodeId *outNewNodeId, UA_Boolean overrideChecks);
+                        UA_NodeId *outNewNodeId);
 
 /* Children, references, type-checking, constructors. */
 UA_StatusCode
 Operation_addNode_finish(UA_Server *server, UA_Session *session,
                          const UA_NodeId *nodeId, const UA_NodeId *parentNodeId,
-                         const UA_NodeId *referenceTypeId, const UA_NodeId *typeDefinitionId,
-                         UA_Boolean overrideChecks);
+                         const UA_NodeId *referenceTypeId, const UA_NodeId *typeDefinitionId);
 
 UA_StatusCode
 UA_Server_addMethodNode_finish(UA_Server *server, const UA_NodeId nodeId,
@@ -352,9 +351,7 @@ UA_Server_addMethodNode_finish(UA_Server *server, const UA_NodeId nodeId,
 /* Create Namespace 0 */
 /**********************/
 
-#ifndef UA_ENABLE_GENERATE_NAMESPACE0
-void UA_Server_createNS0(UA_Server *server);
-#endif
+UA_StatusCode UA_Server_initNS0(UA_Server *server);
 
 #ifdef __cplusplus
 } // extern "C"

Fichier diff supprimé car celui-ci est trop grand
+ 386 - 456
src/server/ua_server_ns0.c


+ 25 - 24
src/server/ua_services_nodemanagement.c

@@ -52,12 +52,16 @@ checkParentReference(UA_Server *server, UA_Session *session, UA_NodeClass nodeCl
        UA_NodeId_isNull(referenceTypeId))
         return UA_STATUSCODE_GOOD;
 
+    /* Omit checks during bootstrap */
+    if(server->bootstrapNS0)
+        return UA_STATUSCODE_GOOD;
+
     /* See if the parent exists */
     const UA_Node *parent = UA_Nodestore_get(server, parentNodeId);
     if(!parent) {
-        UA_LOG_INFO_SESSION(server->config.logger, session, "AddNodes: Parent node not found");
-
-        returnUA_STATUSCODE_BADPARENTNODEIDINVALID;
+        UA_LOG_INFO_SESSION(server->config.logger, session,
+                            "AddNodes: Parent node not found");
+        return UA_STATUSCODE_BADPARENTNODEIDINVALID;
     }
 
     UA_NodeClass parentNodeClass = parent->nodeClass;
@@ -69,8 +73,7 @@ checkParentReference(UA_Server *server, UA_Session *session, UA_NodeClass nodeCl
     if(!referenceType) {
         UA_LOG_INFO_SESSION(server->config.logger, session,
                             "AddNodes: Reference type to the parent not found");
-
-        returnUA_STATUSCODE_BADREFERENCETYPEIDINVALID;
+        return UA_STATUSCODE_BADREFERENCETYPEIDINVALID;
     }
 
     UA_NodeClass referenceTypeNodeClass = referenceType->nodeClass;
@@ -123,9 +126,7 @@ checkParentReference(UA_Server *server, UA_Session *session, UA_NodeClass nodeCl
         return UA_STATUSCODE_BADREFERENCETYPEIDINVALID;
     }
 
-
-
-    returnUA_STATUSCODE_GOOD;
+    return UA_STATUSCODE_GOOD;
 }
 
 static UA_StatusCode
@@ -164,7 +165,7 @@ typeCheckVariableNode(UA_Server *server, UA_Session *session,
         return UA_STATUSCODE_BADTYPEMISMATCH;
 
     /* Typecheck the value */
-    if(value.hasValue) {
+    if(!server->bootstrapNS0 && value.hasValue) {
         /* If the type-check failed write the same value again. The
          * write-service tries to convert to the correct type... */
         if(!compatibleValue(server, &node->dataType, node->valueRank,
@@ -396,7 +397,7 @@ copyChildNode(UA_Server *server, UA_Session *session,
         /* Call addnode_finish, this recursively adds members, the type
          * definition and so on */
         retval = Operation_addNode_finish(server, session, &newNodeId, destinationNodeId,
-                                          &rd->referenceTypeId, typeId, false);
+                                          &rd->referenceTypeId, typeId);
         UA_NodeId_deleteMembers(&newNodeId);
         UA_Nodestore_release(server, type);
     }
@@ -533,7 +534,7 @@ addParentRef(UA_Server *server, UA_Session *session,
 UA_StatusCode
 Operation_addNode_begin(UA_Server *server, UA_Session *session,
                         const UA_AddNodesItem *item, void *nodeContext,
-                        UA_NodeId *outNewNodeId, UA_Boolean overrideChecks) {
+                        UA_NodeId *outNewNodeId) {
     /* Check the namespaceindex */
     if(item->requestedNewNodeId.nodeId.namespaceIndex >= server->namespacesSize) {
         UA_LOG_INFO_SESSION(server->config.logger, session,
@@ -573,7 +574,7 @@ Operation_addNode_begin(UA_Server *server, UA_Session *session,
         return retval;
     }
 
-    if(overrideChecks)
+    if(server->bootstrapNS0)
         goto finished_checks;
 
     /* Use attributes from the typedefinition */
@@ -615,7 +616,7 @@ static const UA_NodeId hasSubtype = {0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_HASSUBT
 UA_StatusCode
 Operation_addNode_finish(UA_Server *server, UA_Session *session, const UA_NodeId *nodeId,
                          const UA_NodeId *parentNodeId, const UA_NodeId *referenceTypeId,
-                         const UA_NodeId *typeDefinitionId, UA_Boolean overrideChecks) {
+                         const UA_NodeId *typeDefinitionId) {
     UA_StatusCode retval = UA_STATUSCODE_GOOD;
     const UA_Node *type = NULL;
 
@@ -624,7 +625,7 @@ Operation_addNode_finish(UA_Server *server, UA_Session *session, const UA_NodeId
     if(!node)
         return UA_STATUSCODE_BADNODEIDUNKNOWN;
 
-    if(overrideChecks)
+    if(server->bootstrapNS0)
         goto get_type;
 
     /* Use the typeDefinition as parent for type-nodes */
@@ -767,14 +768,15 @@ Operation_addNode(UA_Server *server, UA_Session *session, const UA_AddNodesItem
     }
 
     result->statusCode = Operation_addNode_begin(server, session, item, nodeContext,
-                                                 &result->addedNodeId, false);
+                                                 &result->addedNodeId);
     if(result->statusCode != UA_STATUSCODE_GOOD)
         return;
 
+    /* AddNodes_finish */
     result->statusCode =
         Operation_addNode_finish(server, session, &result->addedNodeId,
                                  &item->parentNodeId.nodeId, &item->referenceTypeId,
-                                 &item->typeDefinition.nodeId, false);
+                                 &item->typeDefinition.nodeId);
 
     /* If finishing failed, the node was deleted */
     if(result->statusCode != UA_STATUSCODE_GOOD)
@@ -852,7 +854,7 @@ UA_Server_addNode_begin(UA_Server *server, const UA_NodeClass nodeClass,
     item.nodeAttributes.content.decoded.type = attributeType;
     item.nodeAttributes.content.decoded.data = (void*)(uintptr_t)attr;
     return Operation_addNode_begin(server, &adminSession, &item,
-                                   nodeContext, outNewNodeId, false);
+                                   nodeContext, outNewNodeId);
 }
 
 UA_StatusCode
@@ -861,7 +863,7 @@ UA_Server_addNode_finish(UA_Server *server, const UA_NodeId nodeId,
                          const UA_NodeId referenceTypeId,
                          const UA_NodeId typeDefinitionId) {
     return Operation_addNode_finish(server, &adminSession, &nodeId, &parentNodeId,
-                                    &referenceTypeId, &typeDefinitionId, false);
+                                    &referenceTypeId, &typeDefinitionId);
 }
 
 /****************/
@@ -1239,15 +1241,14 @@ UA_Server_addDataSourceVariableNode(UA_Server *server, const UA_NodeId requested
         outNewNodeId = &newNodeId;
         deleteNodeId = UA_TRUE;
     }
-    UA_StatusCode retval = Operation_addNode_begin(server, &adminSession,
-                                                   &item, nodeContext, outNewNodeId, true);
+    UA_StatusCode retval = Operation_addNode_begin(server, &adminSession, &item,
+                                                   nodeContext, outNewNodeId);
     if(retval != UA_STATUSCODE_GOOD)
         return retval;
     retval = UA_Server_setVariableNode_dataSource(server, *outNewNodeId, dataSource);
     if(retval == UA_STATUSCODE_GOOD)
         retval = Operation_addNode_finish(server, &adminSession, outNewNodeId,
-                                          &parentNodeId, &referenceTypeId,
-                                          &typeDefinition, false);
+                                          &parentNodeId, &referenceTypeId, &typeDefinition);
     if(retval != UA_STATUSCODE_GOOD || deleteNodeId)
         UA_NodeId_deleteMembers(outNewNodeId);
     return retval;
@@ -1361,7 +1362,7 @@ UA_Server_addMethodNode_finish(UA_Server *server, const UA_NodeId nodeId,
 
     /* Call finish to add the parent reference */
     retval |= Operation_addNode_finish(server, &adminSession, &nodeId, &parentNodeId,
-                                       &referenceTypeId, &UA_NODEID_NULL, false);
+                                       &referenceTypeId, &UA_NODEID_NULL);
 
     if(retval != UA_STATUSCODE_GOOD) {
         UA_Server_deleteNode(server, nodeId, true);
@@ -1396,7 +1397,7 @@ UA_Server_addMethodNode(UA_Server *server, const UA_NodeId requestedNewNodeId,
     }
 
     UA_StatusCode retval = Operation_addNode_begin(server, &adminSession, &item,
-                                                   nodeContext, outNewNodeId, false);
+                                                   nodeContext, outNewNodeId);
     if(retval != UA_STATUSCODE_GOOD)
         return retval;
 

+ 10 - 2
tests/check_types_memory.c

@@ -108,7 +108,11 @@ START_TEST(decodeShallFailWithTruncatedBufferButSurvive) {
     //Skip test for void*
     if (_i == UA_TYPES_DISCOVERYCONFIGURATION ||
             _i == UA_TYPES_FILTEROPERAND ||
-            _i == UA_TYPES_MONITORINGFILTER)
+            _i == UA_TYPES_MONITORINGFILTER ||
+            _i == UA_TYPES_UNION ||
+            _i == UA_TYPES_HISTORYREADDETAILS ||
+            _i == UA_TYPES_NOTIFICATIONDATA ||
+            _i == UA_TYPES_MONITORINGFILTERRESULT)
         return;
     // given
     UA_ByteString msg1;
@@ -212,7 +216,11 @@ START_TEST(calcSizeBinaryShallBeCorrect) {
        _i == UA_TYPES_VARIABLETYPEATTRIBUTES ||
        _i == UA_TYPES_FILTEROPERAND ||
        _i == UA_TYPES_MONITORINGFILTER ||
-       _i == UA_TYPES_DISCOVERYCONFIGURATION)
+       _i == UA_TYPES_DISCOVERYCONFIGURATION ||
+       _i == UA_TYPES_UNION ||
+       _i == UA_TYPES_HISTORYREADDETAILS ||
+       _i == UA_TYPES_NOTIFICATIONDATA ||
+       _i == UA_TYPES_MONITORINGFILTERRESULT)
         return;
     void *obj = UA_new(&UA_TYPES[_i]);
     size_t predicted_size = UA_calcSizeBinary(obj, &UA_TYPES[_i]);

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 1514
tools/nodeset_compiler/NodeID_Blacklist_FullNS0.txt


+ 61 - 0
tools/nodeset_compiler/NodeID_NS0_Base.txt

@@ -0,0 +1,61 @@
+i=1
+i=2
+i=3
+i=4
+i=5
+i=6
+i=7
+i=8
+i=9
+i=10
+i=11
+i=12
+i=13
+i=14
+i=15
+i=16
+i=17
+i=18
+i=19
+i=20
+i=21
+i=22
+i=24
+i=26
+i=27
+i=28
+i=31
+i=32
+i=33
+i=34
+i=35
+i=36
+i=37
+i=38
+i=39
+i=40
+i=41
+i=44
+i=45
+i=45
+i=46
+i=47
+i=48
+i=49
+i=58
+i=61
+i=62
+i=63
+i=84
+i=85
+i=86
+i=87
+i=88
+i=89
+i=90
+i=91
+i=121
+i=290
+i=294
+i=295
+i=3048

+ 118 - 42
tools/nodeset_compiler/backend_graphviz.py

@@ -1,26 +1,26 @@
 from nodeset import *
+import graphviz as gv
 
-def NodePrintDot(self):
-    cleanname = "node_" + str(self.id).replace(";","").replace("=","")
-    dot = cleanname + " [label = \"{" + str(self.id) + "|" + str(self.browseName) + \
-                                               "}\", shape=\"record\"]"
-    for r in self.references:
-      if isinstance(r.target, Node):
-        tgtname = "node_" + str(r.target.id).replace(";","").replace("=","")
-        dot = dot + "\n"
-        if r.isForward == True:
-          dot = dot + cleanname + " -> " + tgtname + " [label=\"" + \
-                  str(r.referenceType.browseName) + "\"]\n"
-        else:
-          if len(r.referenceType.inverseName) == 0:
-            logger.warn("Inverse name of reference is null " + str(r.referenceType.id))
-          dot = dot + cleanname + " -> " + tgtname + \
-                " [label=\"" + str(r.referenceType.inverseName) + "\"]\n"
+def nodePrintDot(node):
+    cleanname = "node_" + str(node.id).replace(";", "").replace("=", "")
+    dot = cleanname + " [label = \"{" + str(node.id) + "|" + str(node.browseName) + \
+          "}\", shape=\"record\"]"
+    for r in node.references:
+        if isinstance(r.target, Node):
+            tgtname = "node_" + str(r.target.id).replace(";", "").replace("=", "")
+            dot = dot + "\n"
+            if r.isForward == True:
+                dot = dot + cleanname + " -> " + tgtname + " [label=\"" + \
+                      str(r.referenceType.browseName) + "\"]\n"
+            else:
+                if len(r.referenceType.inverseName) == 0:
+                    logger.warn("Inverse name of reference is null " + str(r.referenceType.id))
+                dot = dot + cleanname + " -> " + tgtname + \
+                      " [label=\"" + str(r.referenceType.inverseName) + "\"]\n"
     return dot
 
-
 def printDotGraphWalk(nodeset, depth=1, filename="out.dot", rootNode=None,
-                        followInverse = False, excludeNodeIds=[]):
+                      followInverse=False, excludeNodeIds=[]):
     """ Outputs a graphiz/dot description the nodes centered around rootNode.
 
         References beginning from rootNode will be followed for depth steps. If
@@ -35,39 +35,115 @@ def printDotGraphWalk(nodeset, depth=1, filename="out.dot", rootNode=None,
     iter = depth
     processed = []
     if rootNode == None or not isinstance(rootNode, Node) or not rootNode in nodeset.nodes:
-      root = nodeset.getRoot()
+        root = nodeset.getRoot()
     else:
-      root = rootNode
+        root = rootNode
 
-    file=open(filename, 'w+')
+    file = open(filename, 'w+')
 
     if root == None:
-      return
+        return
 
     file.write("digraph ns {\n")
-    file.write(root.NodePrintDot())
-    refs=[]
+    file.write(nodePrintDot(root))
+    refs = []
     if followInverse == True:
-      refs = root.references; # + root.getInverseReferences()
+        refs = root.references  # + root.getInverseReferences()
     else:
-      for ref in root.references:
-        if ref.isForward:
-          refs.append(ref)
+        for ref in root.references:
+            if ref.isForward:
+                refs.append(ref)
     while iter > 0:
-      tmp = []
-      for ref in refs:
-        if isinstance(ref.target, Node):
-          tgt = ref.target
-          if not str(tgt.id) in excludeNodeIds:
-            if not tgt in processed:
-              file.write(tgt.NodePrintDot())
-              processed.append(tgt)
-              if ref.isForward == False and followInverse == True:
-                tmp = tmp + tgt.references; # + tgt.getInverseReferences()
-              elif ref.isForward == True :
-                tmp = tmp + tgt.references;
-      refs = tmp
-      iter = iter - 1
+        tmp = []
+        for ref in refs:
+            if isinstance(ref.target, NodeId):
+                tgt = nodeset.nodes[ref.target]
+                if not str(tgt.id) in excludeNodeIds:
+                    if not tgt in processed:
+                        file.write(nodePrintDot(tgt))
+                        processed.append(tgt)
+                        if ref.isForward == False and followInverse == True:
+                            for ref in tgt.inverseReferences:
+                                refs.append(ref)
+                            tmp = tmp + tgt.references  # + tgt.getInverseReferences()
+                        elif ref.isForward == True:
+                            for ref in tgt.references:
+                                refs.append(ref)
+        refs = tmp
+        iter = iter - 1
 
     file.write("}\n")
     file.close()
+
+def getNodeString(node):
+    return node.browseName.name + " (" + str(node.id) + ")"
+
+def getReferenceString(nodeset, ref):
+    refNode = nodeset.nodes[ref.referenceType]
+    return refNode.browseName.name
+
+def getNodeStyle(node):
+    if isinstance(node, ReferenceTypeNode):
+        return {'shape': 'box', 'style': 'filled', 'fillcolor': '1', 'colorscheme': "pastel19"}
+    if isinstance(node, VariableTypeNode):
+        return {'shape': 'box', 'style': 'filled', 'fillcolor': '2', 'colorscheme': "pastel19"}
+    if isinstance(node, ObjectTypeNode):
+        return {'shape': 'box', 'style': 'filled', 'fillcolor': '3', 'colorscheme': "pastel19"}
+    if isinstance(node, DataTypeNode):
+        return {'shape': 'box', 'style': 'filled', 'fillcolor': '4', 'colorscheme': "pastel19"}
+    if isinstance(node, VariableNode):
+        return {'shape': 'ellipse', 'style': 'rounded,filled', 'fillcolor': '5', 'colorscheme': "pastel19"}
+    if isinstance(node, ObjectNode):
+        return {'shape': 'box', 'style': 'rounded,filled', 'fillcolor': '6', 'colorscheme': "pastel19"}
+    if isinstance(node, MethodNode):
+        return {'shape': 'box', 'style': 'rounded,filled', 'fillcolor': '7', 'colorscheme': "pastel19"}
+    if isinstance(node, ViewNode):
+        return {'shape': 'box', 'style': 'rounded,filled', 'fillcolor': '8', 'colorscheme': "pastel19"}
+
+def add_edges(graph, edges):
+    for e in edges:
+        if isinstance(e[0], tuple):
+            graph.edge(*e[0], **e[1])
+        else:
+            graph.edge(*e)
+    return graph
+
+def add_nodes(graph, nodes):
+    for n in nodes:
+        if isinstance(n, tuple):
+            graph.node(n[0], **n[1])
+        else:
+            graph.node(n)
+    return graph
+
+def addReferenceToGraph(nodeset, nodeFrom, nodeTo, reference, graph):
+    add_edges(graph, [((getNodeString(nodeFrom), getNodeString(nodeTo)), {'label': getReferenceString(nodeset, reference)})])
+
+
+def addNodeToGraph(nodeset, node, graph, alreadyAdded=[], relevantReferences=[], isRoot=False, depth = 0):
+    if node.id in alreadyAdded:
+        return
+    alreadyAdded.append(node.id)
+    add_nodes(graph, [(getNodeString(node), getNodeStyle(node))])
+    for ref in node.references:
+        if ref.referenceType in relevantReferences and ref.isForward:
+            targetNode = nodeset.nodes[ref.target]
+            addNodeToGraph(nodeset, targetNode, graph, alreadyAdded, depth=depth+1, relevantReferences=relevantReferences)
+            addReferenceToGraph(nodeset, node, targetNode, ref, graph)
+
+
+def printDependencyGraph(nodeset, filename="dependencies", rootNode=None, excludeNodeIds=[]):
+    if rootNode == None or not isinstance(rootNode, Node) or not rootNode in nodeset.nodes:
+        root = nodeset.getRoot()
+    else:
+        root = rootNode
+
+    if root == None:
+        return
+
+    g = gv.Digraph(name="NodeSet Dependency", format='pdf')
+
+    alreadyAdded = []
+    addNodeToGraph(nodeset, root, g, alreadyAdded, isRoot=True, relevantReferences=nodeset.getRelevantOrderingReferences())
+
+    g.render(filename)

+ 132 - 54
tools/nodeset_compiler/backend_open62541.py

@@ -20,7 +20,9 @@ from __future__ import print_function
 import string
 from collections import deque
 from os.path import basename
-import logging; logger = logging.getLogger(__name__)
+import logging
+
+logger = logging.getLogger(__name__)
 
 from constants import *
 from nodes import *
@@ -32,79 +34,145 @@ from backend_open62541_nodes import generateNodeCode, generateReferenceCode
 ##############
 
 # Select the references that shall be generated after this node in the ordering
+# If both nodes of the reference are hidden we assume that the references between
+# those nodes are already setup. Still print if only the target node is hidden,
+# because we need that reference.
 def selectPrintRefs(nodeset, L, node):
     printRefs = []
     for ref in node.references:
-        if ref.hidden:
-            continue
         targetnode = nodeset.nodes[ref.target]
-        if not targetnode in L:
+        if node.hidden and targetnode.hidden:
+            continue
+        if not targetnode.hidden and not targetnode in L:
             continue
         printRefs.append(ref)
     for ref in node.inverseReferences:
-        if ref.hidden:
-            continue
         targetnode = nodeset.nodes[ref.target]
-        if not targetnode in L:
+        if node.hidden and targetnode.hidden:
+            continue
+        if not targetnode.hidden and not targetnode in L:
             continue
         printRefs.append(ref)
     return printRefs
 
+def addTypeRef(nodeset, type_refs, dataTypeId, referencedById):
+    if not dataTypeId in type_refs:
+        type_refs[dataTypeId] = [referencedById]
+    else:
+        type_refs[dataTypeId].append(referencedById)
+
+
 def reorderNodesMinDependencies(nodeset):
-    #Kahn's algorithm
-    #https://algocoding.wordpress.com/2015/04/05/topological-sorting-python/
-    
-    relevant_types = getSubTypesOf(nodeset,
-                                   nodeset.getNodeByBrowseName("HierarchicalReferences"))
-    relevant_types = map(lambda x: x.id, relevant_types)
+    # Kahn's algorithm
+    # https://algocoding.wordpress.com/2015/04/05/topological-sorting-python/
+
+    relevant_types = nodeset.getRelevantOrderingReferences()
 
     # determine in-degree
-    in_degree = { u.id : 0 for u in nodeset.nodes.values() }
-    for u in nodeset.nodes.values(): # of each node
+    in_degree = {u.id: 0 for u in nodeset.nodes.values()}
+    dataType_refs = {}
+    hiddenCount = 0
+    for u in nodeset.nodes.values():  # of each node
+        if u.hidden:
+            hiddenCount += 1
+            continue
+        hasTypeDef = None
         for ref in u.references:
-            if(ref.referenceType in relevant_types and ref.isForward):
+            if ref.referenceType.i == 40:
+                hasTypeDef = ref.target
+            elif (ref.referenceType in relevant_types and ref.isForward) and not nodeset.nodes[ref.target].hidden:
                 in_degree[ref.target] += 1
-    
+        if hasTypeDef is not None and not nodeset.nodes[hasTypeDef].hidden:
+            # we cannot print the node u because it first needs the variable type node
+            in_degree[u.id] += 1
+
+        if isinstance(u, VariableNode) and u.dataType is not None:
+            dataTypeNode = nodeset.getDataTypeNode(u.dataType)
+            if dataTypeNode is not None and not dataTypeNode.hidden:
+                # we cannot print the node u because it first needs the data type node
+                in_degree[u.id] += 1
+                # to be able to decrement the in_degree count, we need to store it here
+                addTypeRef(nodeset, dataType_refs,dataTypeNode.id, u.id)
+
     # collect nodes with zero in-degree
     Q = deque()
     for id in in_degree:
-      if in_degree[id] == 0:
-          # print referencetypenodes first
-          n = nodeset.nodes[id]
-          if isinstance(n, ReferenceTypeNode):
-              Q.append(nodeset.nodes[id])
-          else:
-              Q.appendleft(nodeset.nodes[id])
- 
-    L = []     # list for order of nodes
+        if in_degree[id] == 0:
+            # print referencetypenodes first
+            n = nodeset.nodes[id]
+            if isinstance(n, ReferenceTypeNode):
+                Q.append(nodeset.nodes[id])
+            else:
+                Q.appendleft(nodeset.nodes[id])
+
+    L = []  # list for order of nodes
     while Q:
-      u = Q.pop()          # choose node of zero in-degree
-      # decide which references to print now based on the ordering
-      u.printRefs = selectPrintRefs(nodeset, L, u)
-      L.append(u)          # and 'remove' it from graph
-      for ref in u.references:
-        if(ref.referenceType in relevant_types and ref.isForward):
-         in_degree[ref.target] -= 1
-         if in_degree[ref.target] == 0:
-           Q.append(nodeset.nodes[ref.target])
-    if len(L) != len(nodeset.nodes.values()):
-      raise Exception("Node graph is circular on the specified references")
+        u = Q.pop()  # choose node of zero in-degree
+        # decide which references to print now based on the ordering
+        u.printRefs = selectPrintRefs(nodeset, L, u)
+        if u.hidden:
+            continue
+
+        L.append(u)  # and 'remove' it from graph
+
+        if isinstance(u, DataTypeNode):
+            # decrement all the nodes which depend on this datatype
+            if u.id in dataType_refs:
+                for n in dataType_refs[u.id]:
+                    if not nodeset.nodes[n].hidden:
+                        in_degree[n] -= 1
+                    if in_degree[n] == 0:
+                        Q.append(nodeset.nodes[n])
+                del dataType_refs[u.id]
+
+        for ref in u.inverseReferences:
+            if ref.referenceType.i == 40:
+                if not nodeset.nodes[ref.target].hidden:
+                    in_degree[ref.target] -= 1
+                if in_degree[ref.target] == 0:
+                    Q.append(nodeset.nodes[ref.target])
+
+        for ref in u.references:
+            if (ref.referenceType in relevant_types and ref.isForward):
+                if not nodeset.nodes[ref.target].hidden:
+                    in_degree[ref.target] -= 1
+                if in_degree[ref.target] == 0:
+                    Q.append(nodeset.nodes[ref.target])
+
+    if len(L) + hiddenCount != len(nodeset.nodes.values()):
+        stillOpen = ""
+        for id in in_degree:
+            if in_degree[id] == 0:
+                continue
+            node = nodeset.nodes[id]
+            stillOpen += node.browseName.name + "/" + str(node.id) + " = " + str(in_degree[id]) + "\r\n"
+        raise Exception("Node graph is circular on the specified references. Still open nodes:\r\n" + stillOpen)
     return L
 
 ###################
 # Generate C Code #
 ###################
 
-def generateOpen62541Code(nodeset, outfilename, supressGenerationOfAttribute = [], generate_ns0 = False):
+def generateOpen62541Code(nodeset, outfilename, supressGenerationOfAttribute=[], generate_ns0=False, typesArray=[]):
     outfilebase = basename(outfilename)
     # Printing functions
     outfileh = open(outfilename + ".h", r"w+")
     outfilec = open(outfilename + ".c", r"w+")
+
     def writeh(line):
         print(unicode(line).encode('utf8'), end='\n', file=outfileh)
+
     def writec(line):
         print(unicode(line).encode('utf8'), end='\n', file=outfilec)
 
+    additionalHeaders = ""
+    if len(typesArray) > 0:
+        for arr in set(typesArray):
+            if arr == "UA_TYPES":
+                continue
+            additionalHeaders += """#include "%s_generated.h"
+                                 """ % arr.lower()
+
     # Print the preamble of the generated code
     writeh("""/* WARNING: This is a generated file.
  * Any manual changes will be overwritten. */
@@ -114,17 +182,17 @@ def generateOpen62541Code(nodeset, outfilename, supressGenerationOfAttribute = [
 
 #ifdef UA_NO_AMALGAMATION
 #include "ua_types.h"
-#include "ua_job.h"
 #include "ua_server.h"
+#include "ua_types_encoding_binary.h"
+%s
 #else
 #include "open62541.h"
-#define NULL ((void *)0)
 #endif
     
-extern void %s(UA_Server *server);
+extern UA_StatusCode %s(UA_Server *server);
 
 #endif /* %s_H_ */""" % \
-           (outfilebase.upper(), outfilebase.upper(), \
+           (outfilebase.upper(), outfilebase.upper(), additionalHeaders,
             outfilebase, outfilebase.upper()))
 
     writec("""/* WARNING: This is a generated file.
@@ -132,32 +200,42 @@ extern void %s(UA_Server *server);
 
 #include "%s.h"
 
-void %s(UA_Server *server) {""" % (outfilebase, outfilebase))
+UA_StatusCode %s(UA_Server *server) {  // NOLINT
+
+UA_StatusCode retVal = UA_STATUSCODE_GOOD;
+""" % (outfilebase, outfilebase))
 
     parentrefs = getSubTypesOf(nodeset, nodeset.getNodeByBrowseName("HierarchicalReferences"))
     parentrefs = map(lambda x: x.id, parentrefs)
 
     # Generate namespaces (don't worry about duplicates)
     writec("/* Use namespace ids generated by the server */")
-    for i,nsid in enumerate(nodeset.namespaces):
-      nsid = nsid.replace("\"","\\\"")
-      writec("UA_UInt16 ns" + str(i) + " = UA_Server_addNamespace(server, \"" + nsid + "\");")
+    for i, nsid in enumerate(nodeset.namespaces):
+        nsid = nsid.replace("\"", "\\\"")
+        writec("UA_UInt16 ns" + str(i) + " = UA_Server_addNamespace(server, \"" + nsid + "\");")
 
     # Loop over the sorted nodes
     logger.info("Reordering nodes for minimal dependencies during printing")
     sorted_nodes = reorderNodesMinDependencies(nodeset)
     logger.info("Writing code for nodes and references")
     for node in sorted_nodes:
-      # Print node
-      if not node.hidden:
-        writec("\n/* " + str(node.displayName) + " - " + str(node.id) + " */")
-        writec(generateNodeCode(node, supressGenerationOfAttribute, generate_ns0, parentrefs))
-
-      # Print inverse references leading to this node
-      for ref in node.printRefs:
-          writec(generateReferenceCode(ref))
+        # Print node
+        if not node.hidden:
+            writec("\n/* " + str(node.displayName) + " - " + str(node.id) + " */")
+            code = generateNodeCode(node, supressGenerationOfAttribute, generate_ns0, parentrefs, nodeset)
+            if code is None:
+                writec("/* Ignored. No parent */")
+                nodeset.hide_node(node.id)
+                continue
+            else:
+                writec(code)
+
+        # Print inverse references leading to this node
+        for ref in node.printRefs:
+            writec(generateReferenceCode(ref))
 
     # Finalize the generated source
+    writec("return retVal;")
     writec("} // closing nodeset()")
     outfileh.close()
     outfilec.close()

+ 57 - 111
tools/nodeset_compiler/backend_open62541_datatypes.py

@@ -1,128 +1,74 @@
 from datatypes import *
+import datetime
+import re
 
 def generateBooleanCode(value):
-  if value:
-    return "true"
-  return "false"
+    if value:
+        return "true"
+    return "false"
 
-def generateStringCode(value):
-  return "UA_STRING(\"" + value + "\")"
+def generateStringCode(value, alloc=False):
+    return "UA_STRING{}(\"{}\")".format("_ALLOC" if alloc else "", value)
 
-def generateXmlElementCode(value):
-  return "UA_XMLELEMENT(\"" + value + "\")"
+def generateXmlElementCode(value, alloc=False):
+    return "UA_XMLELEMENT{}(\"{}\")".format("_ALLOC" if alloc else "", value)
 
-def generateByteStringCode(value):
-  return "UA_BYTESTRING(\"" + value + "\")"
+def generateByteStringCode(value, alloc=False):
+    return "UA_BYTESTRING{}(\"{}\")".format("_ALLOC" if alloc else "", value)
 
-def generateLocalizedTextCode(value):
-  return "UA_LOCALIZEDTEXT(\"" + value.locale + "\", \"" + value.text + "\")"
+def generateLocalizedTextCode(value, alloc=False):
+    return "UA_LOCALIZEDTEXT{}(\"{}\", \"{}\")".format("_ALLOC" if alloc else "", value.locale, value.text)
 
-def generateQualifiedNameCode(value):
-  return "UA_QUALIFIEDNAME(ns" + str(value.ns) + ", \"" + value.name + "\")"
+def generateQualifiedNameCode(value, alloc=False):
+    return "UA_QUALIFIEDNAME{}(ns{}, \"{}\")".format("_ALLOC" if alloc else "", str(value.ns), value.name)
 
 def generateNodeIdCode(value):
-  if not value:
-    return "UA_NODEID_NUMERIC(0,0)"
-  if value.i != None:
-    return "UA_NODEID_NUMERIC(ns%s,%s)" % (value.ns, value.i)
-  elif value.s != None:
-    return "UA_NODEID_STRING(ns%s,%s)" % (value.ns, value.s)
-  raise Exception(str(value) + " no NodeID generation for bytestring and guid..")
+    if not value:
+        return "UA_NODEID_NUMERIC(0,0)"
+    if value.i != None:
+        return "UA_NODEID_NUMERIC(ns%s,%s)" % (value.ns, value.i)
+    elif value.s != None:
+        return "UA_NODEID_STRING(ns%s,%s)" % (value.ns, value.s)
+    raise Exception(str(value) + " no NodeID generation for bytestring and guid..")
 
 def generateExpandedNodeIdCode(value):
     if value.i != None:
-      return "UA_EXPANDEDNODEID_NUMERIC(ns%s, %s)" % (str(value.ns),str(value.i))
+        return "UA_EXPANDEDNODEID_NUMERIC(ns%s, %s)" % (str(value.ns), str(value.i))
     elif value.s != None:
-      return "UA_EXPANDEDNODEID_STRING(ns%s, %s)" % (str(value.ns), value.s)
+        return "UA_EXPANDEDNODEID_STRING(ns%s, %s)" % (str(value.ns), value.s)
     raise Exception(str(value) + " no NodeID generation for bytestring and guid..")
 
-def generateVariantCode(self):
-    code = []
-    valueName = self.parent.getCodePrintableID() + "_variant_DataContents"
-
-    # self.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(self.value) == 0):
-      return code
-    if not isinstance(self.value[0], opcua_value_t):
-      return code
-
-    if self.parent.valueRank() != -1 and \
-       (self.parent.valueRank() >=0 or (len(self.value) > 1 and \
-                                        (self.parent.valueRank() != -2 or self.parent.valueRank() != -3))):
-      # User the following strategy for all directly mappable values a la 'UA_Type MyInt = (UA_Type) 23;'
-      if self.value[0].__binTypeId__ == BUILTINTYPE_TYPEID_GUID:
-        logger.warn("Don't know how to print array of GUID in node " + str(self.parent.id()))
-      elif self.value[0].__binTypeId__ == BUILTINTYPE_TYPEID_DATETIME:
-        logger.warn("Don't know how to print array of DateTime in node " + str(self.parent.id()))
-      elif self.value[0].__binTypeId__ == BUILTINTYPE_TYPEID_DIAGNOSTICINFO:
-        logger.warn("Don't know how to print array of DiagnosticInfo in node " + str(self.parent.id()))
-      elif self.value[0].__binTypeId__ == BUILTINTYPE_TYPEID_STATUSCODE:
-        logger.warn("Don't know how to print array of StatusCode in node " + str(self.parent.id()))
-      else:
-        if self.value[0].__binTypeId__ == BUILTINTYPE_TYPEID_EXTENSIONOBJECT:
-          for v in self.value:
-            logger.debug("Building extObj array index " + str(self.value.index(v)))
-            code.extend(v.printOpen62541CCode_SubType_build(arrayIndex=self.value.index(v)))
-        #code.append("attr.value.type = &UA_TYPES[UA_TYPES_" + self.value[0].stringRepresentation.upper() + "];")
-        code.append("UA_" + self.value[0].stringRepresentation + " " + valueName + \
-                    "[" + str(len(self.value)) + "];")
-        if self.value[0].__binTypeId__ == BUILTINTYPE_TYPEID_EXTENSIONOBJECT:
-          for v in self.value:
-            logger.debug("Printing extObj array index " + str(self.value.index(v)))
-            code.append(valueName + "[" + str(self.value.index(v)) + "] = " + \
-                        v.printOpen62541CCode_SubType(asIndirect=False) + ";")
-            code.append("UA_free(" + v.printOpen62541CCode_SubType() + ");")
-        else:
-          for v in self.value:
-            code.append(valueName + "[" + str(self.value.index(v)) + "] = " + \
-                        v.printOpen62541CCode_SubType() + ";")
-        code.append("UA_Variant_setArray( &attr.value, &" + valueName +
-                    ", (UA_Int32) " + str(len(self.value)) + ", &UA_TYPES[UA_TYPES_" + \
-                    self.value[0].stringRepresentation.upper() + "]);")
-    else:
-      # User the following strategy for all directly mappable values a la 'UA_Type MyInt = (UA_Type) 23;'
-      if self.value[0].__binTypeId__ == BUILTINTYPE_TYPEID_GUID:
-        logger.warn("Don't know how to print scalar GUID in node " + str(self.parent.id()))
-      elif self.value[0].__binTypeId__ == BUILTINTYPE_TYPEID_DATETIME:
-        logger.warn("Don't know how to print scalar DateTime in node " + str(self.parent.id()))
-      elif self.value[0].__binTypeId__ == BUILTINTYPE_TYPEID_DIAGNOSTICINFO:
-        logger.warn("Don't know how to print scalar DiagnosticInfo in node " + str(self.parent.id()))
-      elif self.value[0].__binTypeId__ == BUILTINTYPE_TYPEID_STATUSCODE:
-        logger.warn("Don't know how to print scalar StatusCode in node " + str(self.parent.id()))
-      else:
-        # The following strategy applies to all other types, in particular strings and numerics.
-        if self.value[0].__binTypeId__ == BUILTINTYPE_TYPEID_EXTENSIONOBJECT:
-          code.extend(self.value[0].printOpen62541CCode_SubType_build())
-        #code.append("attr.value.type = &UA_TYPES[UA_TYPES_" + self.value[0].stringRepresentation.upper() + "];")
-        if self.value[0].__binTypeId__ == BUILTINTYPE_TYPEID_EXTENSIONOBJECT:
-          code.append("UA_" + self.value[0].stringRepresentation + " *" + valueName + " = " + \
-                      self.value[0].printOpen62541CCode_SubType() + ";")
-          code.append("UA_Variant_setScalar( &attr.value, " + valueName + ", &UA_TYPES[UA_TYPES_" + \
-                      self.value[0].stringRepresentation.upper() + "]);")
+def generateDateTimeCode(value):
+    epoch = datetime.datetime.utcfromtimestamp(0)
+    mSecsSinceEpoch = (value - epoch).total_seconds() * 1000.0
+    return "( (" + str(mSecsSinceEpoch) + "f * UA_MSEC_TO_DATETIME) + UA_DATETIME_UNIX_EPOCH)"
 
-          #FIXME: There is no membership definition for extensionObjects generated in this function.
-          #code.append("UA_" + self.value[0].stringRepresentation + "_deleteMembers(" + valueName + ");")
-        else:
-          if bootstrapping == True:
-              code.append("UA_Variant* " + self.parent.getCodePrintableID() + "_variant = UA_Variant_new();" )
-          code.append("UA_" + self.value[0].stringRepresentation + " *" + valueName + " =  UA_" + \
-                      self.value[0].stringRepresentation + "_new();")
-          code.append("*" + valueName + " = " + self.value[0].printOpen62541CCode_SubType() + ";")
-          if bootstrapping == False:
-            code.append("UA_Variant_setScalar( &attr.value, " + valueName + ", &UA_TYPES[UA_TYPES_" + \
-                        self.value[0].stringRepresentation.upper() + "]);")
-          else:
-            code.append("UA_Variant_setScalar( "+self.parent.getCodePrintableID()+"_variant, " + \
-                        valueName + ", &UA_TYPES[UA_TYPES_" + self.value[0].stringRepresentation.upper() + "]);")
-          #code.append("UA_" + self.value[0].stringRepresentation + "_deleteMembers(" + valueName + ");")
-    return code
+def generateNodeValueCode(node, instanceName, asIndirect=False):
+    if type(node) in [Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Float, Double]:
+        return "(UA_" + node.__class__.__name__ + ") " + str(node.value)
+    elif type(node) == String:
+        return generateStringCode(node.value, asIndirect)
+    elif type(node) == XmlElement:
+        return generateXmlElementCode(node.value, asIndirect)
+    elif type(node) == ByteString:
+        return generateByteStringCode(re.sub(r"[\r\n]+", "", node.value).replace('"', r'\"'), asIndirect)
+    elif type(node) == LocalizedText:
+        return generateLocalizedTextCode(node, asIndirect)
+    elif type(node) == NodeId:
+        return generateNodeIdCode(node)
+    elif type(node) == ExpandedNodeId:
+        return generateExpandedNodeIdCode(node)
+    elif type(node) == DateTime:
+        return generateDateTimeCode(node.value)
+    elif type(node) == QualifiedName:
+        return generateQualifiedNameCode(node.value, asIndirect)
+    elif type(node) == StatusCode:
+        raise Exception("generateNodeValueCode for type " + node.__class__.name + " not implemented")
+    elif type(node) == DiagnosticInfo:
+        raise Exception("generateNodeValueCode for type " + node.__class__.name + " not implemented")
+    elif type(node) == Guid:
+        raise Exception("generateNodeValueCode for type " + node.__class__.name + " not implemented")
+    elif type(node) == ExtensionObject:
+        if asIndirect == False:
+            return "*" + str(instanceName)
+        return str(instanceName)

+ 366 - 55
tools/nodeset_compiler/backend_open62541_nodes.py

@@ -18,11 +18,17 @@
 
 from nodes import *
 from backend_open62541_datatypes import *
+import re
+import datetime
 
 ###########################################
 # Extract References with Special Meaning #
 ###########################################
 
+import logging
+
+logger = logging.getLogger(__name__)
+
 def extractNodeParent(node, parentrefs):
     """Return a tuple of the most likely (parent, parentReference). The
     parentReference is removed form the inverse references list of the node.
@@ -31,9 +37,10 @@ def extractNodeParent(node, parentrefs):
     for ref in node.inverseReferences:
         if ref.referenceType in parentrefs:
             node.inverseReferences.remove(ref)
-            node.printRefs.remove(ref)
+            if ref in node.printRefs:
+                node.printRefs.remove(ref)
             return (ref.target, ref.referenceType)
-    raise Exception("No node parent known for " + str(node))
+    return None, None
 
 def extractNodeType(node):
     """Returns the most likely type of the variable- or objecttype node. The
@@ -55,115 +62,407 @@ def extractNodeSuperType(node):
 # Generate Code #
 #################
 
+def generateNodeIdPrintable(node):
+    CodePrintable = "NODE_"
+
+    if isinstance(node.id, NodeId):
+        CodePrintable = node.__class__.__name__ + "_" + str(node.id)
+    else:
+        CodePrintable = node.__class__.__name__ + "_unknown_nid"
+
+    return re.sub('[^0-9a-z_]+', '_', CodePrintable.lower())
+
+def generateNodeValueInstanceName(node, parent, recursionDepth, arrayIndex):
+    return generateNodeIdPrintable(parent) + "_" + str(node.alias) + "_" + str(arrayIndex) + "_" + str(recursionDepth)
+
 def generateReferenceCode(reference):
     if reference.isForward:
-        return "UA_Server_addReference(server, %s, %s, %s, true);" % \
-            (generateNodeIdCode(reference.source), \
-             generateNodeIdCode(reference.referenceType), \
-             generateExpandedNodeIdCode(reference.target))
+        return "retVal |= UA_Server_addReference(server, %s, %s, %s, true);" % \
+               (generateNodeIdCode(reference.source),
+                generateNodeIdCode(reference.referenceType),
+                generateExpandedNodeIdCode(reference.target))
     else:
-      return "UA_Server_addReference(server, %s, %s, %s, false);" % \
-          (generateNodeIdCode(reference.source), \
-           generateNodeIdCode(reference.referenceType), \
-           generateExpandedNodeIdCode(reference.target))
+        return "retVal |= UA_Server_addReference(server, %s, %s, %s, false);" % \
+               (generateNodeIdCode(reference.source),
+                generateNodeIdCode(reference.referenceType),
+                generateExpandedNodeIdCode(reference.target))
 
 def generateReferenceTypeNodeCode(node):
     code = []
-    code.append("UA_ReferenceTypeAttributes attr;")
-    code.append("UA_ReferenceTypeAttributes_init(&attr);")
+    code.append("UA_ReferenceTypeAttributes attr = UA_ReferenceTypeAttributes_default;")
     if node.isAbstract:
         code.append("attr.isAbstract = true;")
     if node.symmetric:
         code.append("attr.symmetric  = true;")
     if node.inverseName != "":
-        code.append("attr.inverseName  = UA_LOCALIZEDTEXT_ALLOC(\"en_US\", \"%s\");" % \
+        code.append("attr.inverseName  = UA_LOCALIZEDTEXT(\"\", \"%s\");" % \
                     node.inverseName)
-    return code;
+    return code
 
 def generateObjectNodeCode(node):
     code = []
-    code.append("UA_ObjectAttributes attr;")
-    code.append("UA_ObjectAttributes_init(&attr);")
+    code.append("UA_ObjectAttributes attr = UA_ObjectAttributes_default;")
     if node.eventNotifier:
         code.append("attr.eventNotifier = true;")
-    return code;
+    return code
 
-def generateVariableNodeCode(node):
+def generateVariableNodeCode(node, nodeset):
     code = []
-    code.append("UA_VariableAttributes attr;")
-    code.append("UA_VariableAttributes_init(&attr);")
+    codeCleanup = []
+    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)
     code.append("attr.valueRank = %d;" % node.valueRank)
-    # # The variant is guaranteed to exist by SubtypeEarly()
-    # code.append(getCodePrintableNodeID(node) + ".value.variant.value = *" + \
-    #             getCodePrintableNodeID(node) + "_variant;")
-    # code.append(getCodePrintableNodeID(node) + ".valueSource = UA_VALUESOURCE_VARIANT;")
-    return code
+    if node.valueRank > 0:
+        code.append("attr.arrayDimensionsSize = %d;" % node.valueRank)
+        code.append("attr.arrayDimensions = (UA_UInt32 *)UA_Array_new({}, &UA_TYPES[UA_TYPES_UINT32]);".format(node.valueRank))
+        for dim in range(0, node.valueRank):
+            code.append("attr.arrayDimensions[{}] = 0;".format(dim))
+
+    if node.dataType is not None:
+        if isinstance(node.dataType, NodeId) and node.dataType.ns == 0 and node.dataType.i == 0:
+            #BaseDataType
+            dataTypeNode = nodeset.nodes[NodeId("i=24")]
+        else:
+            dataTypeNode = nodeset.getBaseDataType(nodeset.getDataTypeNode(node.dataType))
+
+        if dataTypeNode is not None:
+            code.append("attr.dataType = %s;" % generateNodeIdCode(dataTypeNode.id))
 
-def generateVariableTypeNodeCode(node):
+            if dataTypeNode.isEncodable():
+                if node.value is not None:
+                    [code1, codeCleanup1] = generateValueCode(node.value, nodeset.nodes[node.id], nodeset)
+                    code += code1
+                    codeCleanup += codeCleanup1
+                else:
+                    code += generateValueCodeDummy(dataTypeNode, nodeset.nodes[node.id], nodeset)
+    return [code, codeCleanup]
+
+def generateVariableTypeNodeCode(node, nodeset):
     code = []
-    code.append("UA_VariableTypeAttributes attr;")
-    code.append("UA_VariableTypeAttributes_init(&attr);")
+    codeCleanup = []
+    code.append("UA_VariableTypeAttributes attr = UA_VariableTypeAttributes_default;")
     if node.historizing:
         code.append("attr.historizing = true;")
-    code.append("attr.valueRank = (UA_Int32)%s;" %str(node.valueRank))
-    # # The variant is guaranteed to exist by SubtypeEarly()
-    # code.append(getCodePrintableNodeID(node) + ".value.variant.value = *" + \
-    #             getCodePrintableNodeID(node) + "_variant;")
-    # code.append(getCodePrintableNodeID(node) + ".valueSource = UA_VALUESOURCE_VARIANT;")
+    code.append("attr.valueRank = (UA_Int32)%s;" % str(node.valueRank))
+    if node.dataType is not None:
+        if isinstance(node.dataType, NodeId) and node.dataType.ns == 0 and node.dataType.i == 0:
+            #BaseDataType
+            dataTypeNode = nodeset.nodes[NodeId("i=24")]
+        else:
+            dataTypeNode = nodeset.getBaseDataType(nodeset.getDataTypeNode(node.dataType))
+        if dataTypeNode is not None:
+            code.append("attr.dataType = %s;" % generateNodeIdCode(dataTypeNode.id))
+            if dataTypeNode.isEncodable():
+                if node.value is not None:
+                    [code1, codeCleanup1] = generateValueCode(node.value, nodeset.nodes[node.id], nodeset)
+                    code += code1
+                    codeCleanup += codeCleanup1
+                else:
+                    code += generateValueCodeDummy(dataTypeNode, nodeset.nodes[node.id], nodeset)
+    return [code, codeCleanup]
+
+def generateExtensionObjectSubtypeCode(node, parent, nodeset, recursionDepth=0, arrayIndex=0):
+    code = [""]
+    codeCleanup = [""]
+
+    logger.debug("Building extensionObject for " + str(parent.id))
+    logger.debug("Value    " + str(node.value))
+    logger.debug("Encoding " + str(node.encodingRule))
+
+    instanceName = generateNodeValueInstanceName(node, parent, recursionDepth, arrayIndex)
+    # If there are any ExtensionObjects instide this ExtensionObject, we need to
+    # generate one-time-structs for them too before we can proceed;
+    for subv in node.value:
+        if isinstance(subv, list):
+            logger.error("ExtensionObject contains an ExtensionObject, which is currently not encodable!")
+
+    code.append("struct {")
+    for field in node.encodingRule:
+        ptrSym = ""
+        # If this is an Array, this is pointer to its contents with a AliasOfFieldSize entry
+        if field[2] != 0:
+            code.append("  UA_Int32 " + str(field[0]) + "Size;")
+            ptrSym = "*"
+        if len(field[1]) == 1:
+            code.append("  UA_" + str(field[1][0]) + " " + ptrSym + str(field[0]) + ";")
+        else:
+            code.append("  UA_ExtensionObject " + " " + ptrSym + str(field[0]) + ";")
+    code.append("} " + instanceName + "_struct;")
+
+    # Assign data to the struct contents
+    # Track the encoding rule definition to detect arrays and/or ExtensionObjects
+    encFieldIdx = 0
+    for subv in node.value:
+        encField = node.encodingRule[encFieldIdx]
+        encFieldIdx = encFieldIdx + 1
+        logger.debug(
+            "Encoding of field " + subv.alias + " is " + str(subv.encodingRule) + "defined by " + str(encField))
+        # Check if this is an array
+        if encField[2] == 0:
+            code.append(instanceName + "_struct." + subv.alias + " = " +
+                        generateNodeValueCode(subv, instanceName, asIndirect=False) + ";")
+        else:
+            if isinstance(subv, list):
+                # this is an array
+                code.append(instanceName + "_struct." + subv.alias + "Size = " + str(len(subv)) + ";")
+                code.append(
+                    instanceName + "_struct." + subv.alias + " = (UA_" + subv.__class__.__name__ +
+                    " *) UA_malloc(sizeof(UA_" + subv.__class__.__name__ + ")*" + str(
+                        len(subv)) + ");")
+                logger.debug("Encoding included array of " + str(len(subv)) + " values.")
+                for subvidx in range(0, len(subv)):
+                    subvv = subv[subvidx]
+                    logger.debug("  " + str(subvidx) + " " + str(subvv))
+                    code.append(instanceName + "_struct." + subv.alias + "[" + str(
+                        subvidx) + "] = " + generateNodeValueCode(subvv, instanceName) + ";")
+                code.append("}")
+            else:
+                code.append(instanceName + "_struct." + subv.alias + "Size = 1;")
+                code.append(
+                    instanceName + "_struct." + subv.alias + " = (UA_" + subv.__class__.__name__ +
+                    " *) UA_malloc(sizeof(UA_" + subv.__class__.__name__ + "));")
+                code.append(instanceName + "_struct." + subv.alias + "[0]  = " +
+                            generateNodeValueCode(subv, instanceName, asIndirect=True) + ";")
+
+    # Allocate some memory
+    code.append("UA_ExtensionObject *" + instanceName + " =  UA_ExtensionObject_new();")
+    codeCleanup.append("UA_ExtensionObject_delete(" + instanceName + ");")
+    code.append(instanceName + "->encoding = UA_EXTENSIONOBJECT_ENCODED_BYTESTRING;")
+    #if parent.dataType.ns == 0:
+
+    binaryEncodingId = nodeset.getBinaryEncodingIdForNode(parent.dataType)
+    code.append(
+        instanceName + "->content.encoded.typeId = UA_NODEID_NUMERIC(" + str(binaryEncodingId.ns) + ", " +
+        str(binaryEncodingId.i) + ");")
+    code.append(
+        "if(UA_ByteString_allocBuffer(&" + instanceName + "->content.encoded.body, 65000) != UA_STATUSCODE_GOOD) {}")
+
+    # Encode each value as a bytestring seperately.
+    code.append("UA_Byte *pos" + instanceName + " = " + instanceName + "->content.encoded.body.data;")
+    code.append("const UA_Byte *end" + instanceName + " = &" + instanceName + "->content.encoded.body.data[65000];")
+    encFieldIdx = 0
+    code.append("{")
+    for subv in node.value:
+        encField = node.encodingRule[encFieldIdx]
+        encFieldIdx = encFieldIdx + 1
+        if encField[2] == 0:
+            code.append(
+                "retVal |= UA_encodeBinary(&" + instanceName + "_struct." + subv.alias + ", " +
+                getTypesArrayForValue(nodeset, subv) + ", &pos" + instanceName + ", &end" + instanceName + ", NULL, NULL);")
+        else:
+            if isinstance(subv, list):
+                for subvidx in range(0, len(subv)):
+                    code.append("retVal |= UA_encodeBinary(&" + instanceName + "_struct." + subv.alias + "[" +
+                                str(subvidx) + "], " + getTypesArrayForValue(nodeset, subv) + ", &pos" +
+                                instanceName + ", &end" + instanceName + ", NULL, NULL);")
+            else:
+                code.append(
+                    "retVal |= UA_encodeBinary(&" + instanceName + "_struct." + subv.alias + "[0], " +
+                    getTypesArrayForValue(nodeset, subv) + ", &pos" + instanceName + ", &end" + instanceName + ", NULL, NULL);")
+
+    code.append("}")
+    # Reallocate the memory by swapping the 65k Bytestring for a new one
+    code.append("size_t " + instanceName + "_encOffset = (uintptr_t)(" +
+                "pos" + instanceName + "-" + instanceName + "->content.encoded.body.data);")
+    code.append(instanceName + "->content.encoded.body.length = " + instanceName + "_encOffset;")
+    code.append("UA_Byte *" + instanceName + "_newBody = (UA_Byte *) UA_malloc(" + instanceName + "_encOffset );")
+    code.append("memcpy(" + instanceName + "_newBody, " + instanceName + "->content.encoded.body.data, " +
+                instanceName + "_encOffset);")
+    code.append("UA_Byte *" + instanceName + "_oldBody = " + instanceName + "->content.encoded.body.data;")
+    code.append(instanceName + "->content.encoded.body.data = " + instanceName + "_newBody;")
+    code.append("UA_free(" + instanceName + "_oldBody);")
+    code.append("")
+    return [code, codeCleanup]
+
+
+def generateValueCodeDummy(dataTypeNode, parentNode, nodeset, bootstrapping=True):
+    code = []
+    valueName = generateNodeIdPrintable(parentNode) + "_variant_DataContents"
+
+    typeArr = dataTypeNode.typesArray + "[" + dataTypeNode.typesArray + "_" + dataTypeNode.browseName.name.upper() + "]"
+    typeStr = "UA_" + dataTypeNode.browseName.name
+
+    if parentNode.valueRank > 0:
+        code.append(typeStr + " *" + valueName + " = (" + typeStr + "*) UA_alloca(" + typeArr + ".memSize * " + str(parentNode.valueRank) + ");")
+        for i in range(0, parentNode.valueRank):
+            code.append("UA_init(&" + valueName + "[" + str(i) + "], &" + typeArr + ");")
+            code.append("UA_Variant_setArray( &attr.value, " + valueName + ", (UA_Int32) " +
+                        str(parentNode.valueRank) + ", &" + typeArr + ");")
+    else:
+        code.append("void *" + valueName + " = UA_alloca(" + typeArr + ".memSize);")
+        code.append("UA_init(" + valueName + ", &" + typeArr + ");")
+        code.append("UA_Variant_setScalar(&attr.value, " + valueName + ", &" + typeArr + ");")
+
     return code
 
+def getTypesArrayForValue(nodeset, value):
+    typeNode = nodeset.getNodeByBrowseName(value.__class__.__name__)
+    if typeNode is None:
+        typesArray = "UA_TYPES"
+    else:
+        typesArray = typeNode.typesArray
+    return "&" + typesArray + "[" + typesArray + "_" + \
+                    value.__class__.__name__.upper() + "]"
+
+def generateValueCode(node, parentNode, nodeset, bootstrapping=True):
+    code = []
+    codeCleanup = []
+    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 ""
+
+    if parentNode.valueRank != -1 and (parentNode.valueRank >= 0
+                                       or (len(node.value) > 1
+                                           and (parentNode.valueRank != -2 or parentNode.valueRank != -3))):
+        # User the following strategy for all directly mappable values a la 'UA_Type MyInt = (UA_Type) 23;'
+        if node.value[0].numericRepresentation == BUILTINTYPE_TYPEID_GUID:
+            logger.warn("Don't know how to print array of GUID in node " + str(parentNode.id))
+        elif node.value[0].numericRepresentation == BUILTINTYPE_TYPEID_DATETIME:
+            logger.warn("Don't know how to print array of DateTime in node " + str(parentNode.id))
+        elif node.value[0].numericRepresentation == BUILTINTYPE_TYPEID_DIAGNOSTICINFO:
+            logger.warn("Don't know how to print array of DiagnosticInfo in node " + str(parentNode.id))
+        elif node.value[0].numericRepresentation == BUILTINTYPE_TYPEID_STATUSCODE:
+            logger.warn("Don't know how to print array of StatusCode in node " + str(parentNode.id))
+        else:
+            if node.value[0].numericRepresentation == BUILTINTYPE_TYPEID_EXTENSIONOBJECT:
+                for idx, v in enumerate(node.value):
+                    logger.debug("Building extObj array index " + str(idx))
+                    [code1, codeCleanup1] = generateExtensionObjectSubtypeCode(v, parent=parentNode, nodeset=nodeset, arrayIndex=idx)
+                    code = code + code1
+                    codeCleanup = codeCleanup + codeCleanup1
+            code.append("UA_" + node.value[0].__class__.__name__ + " " + valueName + "[" + str(len(node.value)) + "];")
+            if node.value[0].numericRepresentation == BUILTINTYPE_TYPEID_EXTENSIONOBJECT:
+                for idx, v in enumerate(node.value):
+                    logger.debug("Printing extObj array index " + str(idx))
+                    instanceName = generateNodeValueInstanceName(v, parentNode, 0, idx)
+                    code.append(
+                        valueName + "[" + str(idx) + "] = " +
+                        generateNodeValueCode(v, instanceName) + ";")
+                    # code.append("UA_free(&" +valueName + "[" + str(idx) + "]);")
+            else:
+                for idx, v in enumerate(node.value):
+                    instanceName = generateNodeValueInstanceName(v, parentNode, 0, idx)
+                    code.append(
+                        valueName + "[" + str(idx) + "] = " + generateNodeValueCode(v, instanceName) + ";")
+            code.append("UA_Variant_setArray( &attr.value, &" + valueName +
+                        ", (UA_Int32) " + str(len(node.value)) + ", " +
+                        getTypesArrayForValue(nodeset, node.value[0]) + ");")
+    else:
+        # User the following strategy for all directly mappable values a la 'UA_Type MyInt = (UA_Type) 23;'
+        if node.value[0].numericRepresentation == BUILTINTYPE_TYPEID_GUID:
+            logger.warn("Don't know how to print scalar GUID in node " + str(parentNode.id))
+        elif node.value[0].numericRepresentation == BUILTINTYPE_TYPEID_DATETIME:
+            logger.warn("Don't know how to print scalar DateTime in node " + str(parentNode.id))
+        elif node.value[0].numericRepresentation == BUILTINTYPE_TYPEID_DIAGNOSTICINFO:
+            logger.warn("Don't know how to print scalar DiagnosticInfo in node " + str(parentNode.id))
+        elif node.value[0].numericRepresentation == BUILTINTYPE_TYPEID_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 node.value[0].numericRepresentation == BUILTINTYPE_TYPEID_EXTENSIONOBJECT:
+                [code1, codeCleanup1] = generateExtensionObjectSubtypeCode(node.value[0], parent=parentNode, nodeset=nodeset)
+                code = code + code1
+                codeCleanup = codeCleanup + codeCleanup1
+            instanceName = generateNodeValueInstanceName(node.value[0], parentNode, 0, 0)
+            if node.value[0].numericRepresentation == BUILTINTYPE_TYPEID_EXTENSIONOBJECT:
+                code.append("UA_" + node.value[0].__class__.__name__ + " *" + valueName + " = " +
+                            generateNodeValueCode(node.value[0], instanceName) + ";")
+                code.append(
+                    "UA_Variant_setScalar( &attr.value, " + valueName + ", " +
+                    getTypesArrayForValue(nodeset, node.value[0]) + ");")
+
+                # FIXME: There is no membership definition for extensionObjects generated in this function.
+                # code.append("UA_" + node.value[0].__class__.__name__ + "_deleteMembers(" + valueName + ");")
+            else:
+                code.append("UA_" + node.value[0].__class__.__name__ + " *" + valueName + " =  UA_" + node.value[
+                    0].__class__.__name__ + "_new();")
+                code.append("*" + valueName + " = " + generateNodeValueCode(node.value[0], instanceName) + ";")
+                code.append(
+                        "UA_Variant_setScalar( &attr.value, " + valueName + ", " +
+                        getTypesArrayForValue(nodeset, node.value[0]) + ");")
+    return [code, codeCleanup]
+
 def generateMethodNodeCode(node):
     code = []
-    code.append("UA_MethodAttributes attr;")
-    code.append("UA_MethodAttributes_init(&attr);")
+    code.append("UA_MethodAttributes attr = UA_MethodAttributes_default;")
     if node.executable:
-      code.append("attr.executable = true;")
+        code.append("attr.executable = true;")
     if node.userExecutable:
-      code.append("attr.userExecutable = true;")
+        code.append("attr.userExecutable = true;")
     return code
 
 def generateObjectTypeNodeCode(node):
     code = []
-    code.append("UA_ObjectTypeAttributes attr;")
-    code.append("UA_ObjectTypeAttributes_init(&attr);")
+    code.append("UA_ObjectTypeAttributes attr = UA_ObjectTypeAttributes_default;")
     if node.isAbstract:
-      code.append("attr.isAbstract = true;")
+        code.append("attr.isAbstract = true;")
     return code
 
 def generateDataTypeNodeCode(node):
     code = []
-    code.append("UA_DataTypeAttributes attr;")
-    code.append("UA_DataTypeAttributes_init(&attr);")
+    code.append("UA_DataTypeAttributes attr = UA_DataTypeAttributes_default;")
     if node.isAbstract:
-      code.append("attr.isAbstract = true;")
+        code.append("attr.isAbstract = true;")
     return code
 
 def generateViewNodeCode(node):
     code = []
-    code.append("UA_ViewAttributes attr;")
-    code.append("UA_ViewAttributes_init(&attr);")
+    code.append("UA_ViewAttributes attr = UA_ViewAttributes_default;")
     if node.containsNoLoops:
-      code.append("attr.containsNoLoops = true;")
+        code.append("attr.containsNoLoops = true;")
     code.append("attr.eventNotifier = (UA_Byte)%s;" % str(node.eventNotifier))
     return code
 
-def generateNodeCode(node, supressGenerationOfAttribute, generate_ns0, parentrefs):
+def getNodeTypeDefinition(node):
+    for ref in node.references:
+        # 40 = HasTypeDefinition
+        if ref.referenceType.i == 40:
+            return ref.target
+    return None
+
+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(node, supressGenerationOfAttribute, generate_ns0, parentrefs, nodeset):
     code = []
     code.append("{")
 
+    codeCleanup = []
+
     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):
-        code.extend(generateVariableNodeCode(node))
+        [code1, codeCleanup1] = generateVariableNodeCode(node, nodeset)
+        code.extend(code1)
+        codeCleanup.extend(codeCleanup1)
     elif isinstance(node, VariableTypeNode):
-        code.extend(generateVariableTypeNodeCode(node))
+        [code1, codeCleanup1] = generateVariableTypeNodeCode(node, nodeset)
+        code.extend(code1)
+        codeCleanup.extend(codeCleanup1)
     elif isinstance(node, MethodNode):
         code.extend(generateMethodNodeCode(node))
     elif isinstance(node, ObjectTypeNode):
@@ -177,23 +476,35 @@ def generateNodeCode(node, supressGenerationOfAttribute, generate_ns0, parentref
     code.append("attr.description = " + generateLocalizedTextCode(node.description) + ";")
     code.append("attr.writeMask = %d;" % node.writeMask)
     code.append("attr.userWriteMask = %d;" % node.userWriteMask)
-    
-    if not generate_ns0:
+
+
+    typeDef = getNodeTypeDefinition(node)
+    isDataTypeEncodingType = typeDef is not None and typeDef.ns == 0 and typeDef.i == 76
+
+    # Object nodes of type DataTypeEncoding do not have any parent
+    if not generate_ns0 and not isDataTypeEncodingType:
         (parentNode, parentRef) = extractNodeParent(node, parentrefs)
+        if parentNode is None or parentRef is None:
+            return None
     else:
         (parentNode, parentRef) = (NodeId(), NodeId())
 
-    code.append("UA_Server_add%s(server," % node.__class__.__name__)
+    code.append("retVal |= UA_Server_add%s(server," % node.__class__.__name__)
     code.append(generateNodeIdCode(node.id) + ",")
     code.append(generateNodeIdCode(parentNode) + ",")
     code.append(generateNodeIdCode(parentRef) + ",")
     code.append(generateQualifiedNameCode(node.browseName) + ",")
-    if (isinstance(node, VariableNode) and not isinstance(node, VariableTypeNode)) or isinstance(node, ObjectNode):
-        code.append("UA_NODEID_NUMERIC(0,0),") # parent
+    if isinstance(node, VariableTypeNode):
+        # we need the HasSubtype reference
+        code.append(generateSubtypeOfDefinitionCode(node) + ",")
+    elif isinstance(node, VariableNode) or isinstance(node, ObjectNode):
+        typeDefCode = "UA_NODEID_NULL" if typeDef is None else generateNodeIdCode(typeDef)
+        code.append(typeDefCode + ",")
     code.append("attr,")
     if isinstance(node, MethodNode):
-        code.append("NULL, NULL, 0, NULL, 0, NULL, NULL);")
+        code.append("NULL, 0, NULL, 0, NULL, NULL, NULL);")
     else:
         code.append("NULL, NULL);")
+    code.extend(codeCleanup)
     code.append("}\n")
     return "\n".join(code)

+ 13 - 10
tools/nodeset_compiler/constants.py

@@ -20,18 +20,18 @@
 ### this program.
 ###
 
-NODE_CLASS_GENERERIC        = 0
-NODE_CLASS_OBJECT           = 1
-NODE_CLASS_VARIABLE         = 2
-NODE_CLASS_METHOD           = 4
-NODE_CLASS_OBJECTTYPE       = 8
-NODE_CLASS_VARIABLETYPE     = 16
-NODE_CLASS_REFERENCETYPE    = 32
-NODE_CLASS_DATATYPE         = 64
-NODE_CLASS_VIEW             = 128
+NODE_CLASS_GENERERIC = 0
+NODE_CLASS_OBJECT = 1
+NODE_CLASS_VARIABLE = 2
+NODE_CLASS_METHOD = 4
+NODE_CLASS_OBJECTTYPE = 8
+NODE_CLASS_VARIABLETYPE = 16
+NODE_CLASS_REFERENCETYPE = 32
+NODE_CLASS_DATATYPE = 64
+NODE_CLASS_VIEW = 128
 
 # Not in OPC-UA, but exists in XML
-NODE_CLASS_METHODTYPE       = 256
+NODE_CLASS_METHODTYPE = 256
 
 ##
 ## Numeric codes used to encode binary type fields:
@@ -59,3 +59,6 @@ BUILTINTYPE_TYPEID_STRING = 20
 BUILTINTYPE_TYPEID_XMLELEMENT = 21
 BUILTINTYPE_TYPEID_BYTESTRING = 22
 BUILTINTYPE_TYPEID_DIAGNOSTICINFO = 23
+BUILTINTYPE_TYPEID_NUMBER = 24
+BUILTINTYPE_TYPEID_UINTEGER = 25
+BUILTINTYPE_TYPEID_INTEGER = 26

Fichier diff supprimé car celui-ci est trop grand
+ 660 - 460
tools/nodeset_compiler/datatypes.py


+ 562 - 294
tools/nodeset_compiler/nodes.py

@@ -25,318 +25,586 @@ from constants import *
 logger = logging.getLogger(__name__)
 
 if sys.version_info[0] >= 3:
-  # strings are already parsed to unicode
-  def unicode(s):
-    return s
+    # strings are already parsed to unicode
+    def unicode(s):
+        return s
 
 class Reference(object):
-  # all either nodeids or strings with an alias
-  def __init__(self, source, referenceType, target, isForward = True, hidden = False):
-    self.source = source
-    self.referenceType = referenceType
-    self.target = target
-    self.isForward = isForward
-    self.hidden = hidden # the reference is part of a nodeset that already exists
-
-  def __str__(self):
-    retval = str(self.source)
-    if not self.isForward:
-      retval = retval + "<"
-    retval = retval + "--[" + str(self.referenceType) + "]--"
-    if self.isForward:
-      retval = retval + ">"
-    return retval + str(self.target)
-
-  def __repr__(self):
-      return str(self)
-
-  def __eq__(self, other):
-      return str(self) == str(other)
-
-  def __hash__(self):
-    return hash(str(self))
+    # all either nodeids or strings with an alias
+    def __init__(self, source, referenceType, target, isForward=True, hidden=False, inferred=False):
+        self.source = source
+        self.referenceType = referenceType
+        self.target = target
+        self.isForward = isForward
+        self.hidden = hidden  # the reference is part of a nodeset that already exists
+        self.inferred = inferred
+
+    def __str__(self):
+        retval = str(self.source)
+        if not self.isForward:
+            retval = retval + "<"
+        retval = retval + "--[" + str(self.referenceType) + "]--"
+        if self.isForward:
+            retval = retval + ">"
+        return retval + str(self.target)
+
+    def __repr__(self):
+        return str(self)
+
+    def __eq__(self, other):
+        return str(self) == str(other)
+
+    def __hash__(self):
+        return hash(str(self))
 
 class Node(object):
-  def __init__(self):
-    self.id             = NodeId()
-    self.nodeClass      = NODE_CLASS_GENERERIC
-    self.browseName     = QualifiedName()
-    self.displayName    = LocalizedText()
-    self.description    = LocalizedText()
-    self.writeMask      = 0
-    self.userWriteMask  = 0
-    self.references     = Set()
-    self.inverseReferences = Set()
-    self.hidden = False
-
-  def __str__(self):
-    return self.__class__.__name__ + "(" + str(self.id) + ")"
-
-  def __repr__(self):
-    return str(self)
-
-  def sanitize(self):
-    pass
-
-  def parseXML(self, xmlelement):
-    for idname in ['NodeId', 'NodeID', 'nodeid']:
-      if xmlelement.hasAttribute(idname):
-        self.id = NodeId(xmlelement.getAttribute(idname))
-
-    for (at, av) in xmlelement.attributes.items():
-      if at == "BrowseName":
-        self.browseName = QualifiedName(av)
-      elif at == "DisplayName":
-        self.displayName = LocalizedText(av)
-      elif at == "Description":
-        self.description = LocalizedText(av)
-      elif at == "WriteMask":
-        self.writeMask = int(av)
-      elif at == "UserWriteMask":
-        self.userWriteMask = int(av)
-      elif at == "EventNotifier":
-        self.eventNotifier = int(av)
-
-    for x in xmlelement.childNodes:
-      if x.nodeType != x.ELEMENT_NODE:
-        continue
-      if x.firstChild:
-        if x.tagName == "BrowseName":
-          self.browseName = QualifiedName(x.firstChild.data)
-        elif x.tagName == "DisplayName":
-          self.displayName = LocalizedText(x.firstChild.data)
-        elif x.tagName == "Description":
-          self.description = LocalizedText(x.firstChild.data)
-        elif x.tagName == "WriteMask":
-          self.writeMask = int(unicode(x.firstChild.data))
-        elif x.tagName == "UserWriteMask":
-          self.userWriteMask = int(unicode(x.firstChild.data))
-        if x.tagName == "References":
-          self.parseXMLReferences(x)
-
-  def parseXMLReferences(self, xmlelement):
-    for ref in xmlelement.childNodes:
-      if ref.nodeType != ref.ELEMENT_NODE:
-        continue
-      source = NodeId(str(self.id)) # deep-copy of the nodeid
-      target = NodeId(ref.firstChild.data)
-      reftype = None
-      forward = True
-      for (at, av) in ref.attributes.items():
-        if at == "ReferenceType":
-          if '=' in av:
-            reftype = NodeId(av)
-          else:
-            reftype = av # alias, such as "HasSubType"
-        elif at == "IsForward":
-          forward = not "false" in av.lower()
-      if forward:
-        self.references.add(Reference(source, reftype, target, forward))
-      else:
-        self.inverseReferences.add(Reference(source, reftype, target, forward))
-
-  def replaceAliases(self, aliases):
-    if str(self.id) in aliases:
-      self.id = NodeId(aliases[self.id])
-    new_refs = set()
-    for ref in self.references:
-      if str(ref.source) in aliases:
-        ref.source = NodeId(aliases[ref.source])
-      if str(ref.target) in aliases:
-        ref.target = NodeId(aliases[ref.target])
-      if str(ref.referenceType) in aliases:
-        ref.referenceType = NodeId(aliases[ref.referenceType])
-      new_refs.add(ref)
-    self.references = new_refs
-    new_inv_refs = set()
-    for ref in self.inverseReferences:
-      if str(ref.source) in aliases:
-        ref.source = NodeId(aliases[ref.source])
-      if str(ref.target) in aliases:
-        ref.target = NodeId(aliases[ref.target])
-      if str(ref.referenceType) in aliases:
-        ref.referenceType = NodeId(aliases[ref.referenceType])
-      new_inv_refs.add(ref)
-    self.inverseReferences = new_inv_refs
-
-  def replaceNamespaces(self, nsMapping):
-    self.id.ns = nsMapping[self.id.ns]
-    self.browseName.ns = nsMapping[self.browseName.ns]
-    new_refs = set()
-    for ref in self.references:
-      ref.source.ns = nsMapping[ref.source.ns]
-      ref.target.ns = nsMapping[ref.target.ns]
-      ref.referenceType.ns = nsMapping[ref.referenceType.ns]
-      new_refs.add(ref)
-    self.references = new_refs
-    new_inv_refs = set()
-    for ref in self.inverseReferences:
-      ref.source.ns = nsMapping[ref.source.ns]
-      ref.target.ns = nsMapping[ref.target.ns]
-      ref.referenceType.ns = nsMapping[ref.referenceType.ns]
-      new_inv_refs.add(ref)
-    self.inverseReferences = new_inv_refs
+    def __init__(self):
+        self.id = NodeId()
+        self.nodeClass = NODE_CLASS_GENERERIC
+        self.browseName = QualifiedName()
+        self.displayName = LocalizedText()
+        self.description = LocalizedText()
+        self.symbolicName = String()
+        self.writeMask = 0
+        self.userWriteMask = 0
+        self.references = Set()
+        self.inverseReferences = Set()
+        self.hidden = False
+
+    def __str__(self):
+        return self.__class__.__name__ + "(" + str(self.id) + ")"
+
+    def __repr__(self):
+        return str(self)
+
+    def sanitize(self):
+        pass
+
+    def parseXML(self, xmlelement):
+        for idname in ['NodeId', 'NodeID', 'nodeid']:
+            if xmlelement.hasAttribute(idname):
+                self.id = NodeId(xmlelement.getAttribute(idname))
+
+        for (at, av) in xmlelement.attributes.items():
+            if at == "BrowseName":
+                self.browseName = QualifiedName(av)
+            elif at == "DisplayName":
+                self.displayName = LocalizedText(av)
+            elif at == "Description":
+                self.description = LocalizedText(av)
+            elif at == "WriteMask":
+                self.writeMask = int(av)
+            elif at == "UserWriteMask":
+                self.userWriteMask = int(av)
+            elif at == "EventNotifier":
+                self.eventNotifier = int(av)
+            elif at == "SymbolicName":
+                self.symbolicName = String(av)
+
+        for x in xmlelement.childNodes:
+            if x.nodeType != x.ELEMENT_NODE:
+                continue
+            if x.firstChild:
+                if x.localName == "BrowseName":
+                    self.browseName = QualifiedName(x.firstChild.data)
+                elif x.localName == "DisplayName":
+                    self.displayName = LocalizedText(x.firstChild.data)
+                elif x.localName == "Description":
+                    self.description = LocalizedText(x.firstChild.data)
+                elif x.localName == "WriteMask":
+                    self.writeMask = int(unicode(x.firstChild.data))
+                elif x.localName == "UserWriteMask":
+                    self.userWriteMask = int(unicode(x.firstChild.data))
+                if x.localName == "References":
+                    self.parseXMLReferences(x)
+
+    def parseXMLReferences(self, xmlelement):
+        for ref in xmlelement.childNodes:
+            if ref.nodeType != ref.ELEMENT_NODE:
+                continue
+            source = NodeId(str(self.id))  # deep-copy of the nodeid
+            target = NodeId(ref.firstChild.data)
+            reftype = None
+            forward = True
+            for (at, av) in ref.attributes.items():
+                if at == "ReferenceType":
+                    if '=' in av:
+                        reftype = NodeId(av)
+                    else:
+                        reftype = av  # alias, such as "HasSubType"
+                elif at == "IsForward":
+                    forward = not "false" in av.lower()
+            if forward:
+                self.references.add(Reference(source, reftype, target, forward))
+            else:
+                self.inverseReferences.add(Reference(source, reftype, target, forward))
+
+    def replaceAliases(self, aliases):
+        if str(self.id) in aliases:
+            self.id = NodeId(aliases[self.id])
+        new_refs = set()
+        for ref in self.references:
+            if str(ref.source) in aliases:
+                ref.source = NodeId(aliases[ref.source])
+            if str(ref.target) in aliases:
+                ref.target = NodeId(aliases[ref.target])
+            if str(ref.referenceType) in aliases:
+                ref.referenceType = NodeId(aliases[ref.referenceType])
+            new_refs.add(ref)
+        self.references = new_refs
+        new_inv_refs = set()
+        for ref in self.inverseReferences:
+            if str(ref.source) in aliases:
+                ref.source = NodeId(aliases[ref.source])
+            if str(ref.target) in aliases:
+                ref.target = NodeId(aliases[ref.target])
+            if str(ref.referenceType) in aliases:
+                ref.referenceType = NodeId(aliases[ref.referenceType])
+            new_inv_refs.add(ref)
+        self.inverseReferences = new_inv_refs
+
+    def replaceNamespaces(self, nsMapping):
+        self.id.ns = nsMapping[self.id.ns]
+        self.browseName.ns = nsMapping[self.browseName.ns]
+        new_refs = set()
+        for ref in self.references:
+            ref.source.ns = nsMapping[ref.source.ns]
+            ref.target.ns = nsMapping[ref.target.ns]
+            ref.referenceType.ns = nsMapping[ref.referenceType.ns]
+            new_refs.add(ref)
+        self.references = new_refs
+        new_inv_refs = set()
+        for ref in self.inverseReferences:
+            ref.source.ns = nsMapping[ref.source.ns]
+            ref.target.ns = nsMapping[ref.target.ns]
+            ref.referenceType.ns = nsMapping[ref.referenceType.ns]
+            new_inv_refs.add(ref)
+        self.inverseReferences = new_inv_refs
 
 class ReferenceTypeNode(Node):
-  def __init__(self, xmlelement = None):
-    Node.__init__(self)
-    self.nodeClass = NODE_CLASS_REFERENCETYPE
-    self.isAbstract    = False
-    self.symmetric     = False
-    self.inverseName   = ""
-    if xmlelement:
-      self.parseXML(xmlelement)
-
-  def parseXML(self, xmlelement):
-    Node.parseXML(self, xmlelement)
-    for (at, av) in xmlelement.attributes.items():
-      if at == "Symmetric":
-        self.symmetric = "false" not in av.lower()
-      elif at == "InverseName":
-        self.inverseName = str(av)
-      elif at == "IsAbstract":
-        self.isAbstract = "false" not in av.lower()
-
-    for x in xmlelement.childNodes:
-      if x.nodeType == x.ELEMENT_NODE:
-        if x.tagName == "InverseName" and x.firstChild:
-          self.inverseName = str(unicode(x.firstChild.data))
+    def __init__(self, xmlelement=None):
+        Node.__init__(self)
+        self.nodeClass = NODE_CLASS_REFERENCETYPE
+        self.isAbstract = False
+        self.symmetric = False
+        self.inverseName = ""
+        if xmlelement:
+            self.parseXML(xmlelement)
+
+    def parseXML(self, xmlelement):
+        Node.parseXML(self, xmlelement)
+        for (at, av) in xmlelement.attributes.items():
+            if at == "Symmetric":
+                self.symmetric = "false" not in av.lower()
+            elif at == "InverseName":
+                self.inverseName = str(av)
+            elif at == "IsAbstract":
+                self.isAbstract = "false" not in av.lower()
+
+        for x in xmlelement.childNodes:
+            if x.nodeType == x.ELEMENT_NODE:
+                if x.localName == "InverseName" and x.firstChild:
+                    self.inverseName = str(unicode(x.firstChild.data))
 
 class ObjectNode(Node):
-  def __init__(self, xmlelement = None):
-    Node.__init__(self)
-    self.nodeClass = NODE_CLASS_OBJECT
-    self.eventNotifier = 0
-    if xmlelement:
-      self.parseXML(xmlelement)
-
-  def parseXML(self, xmlelement):
-    Node.parseXML(self, xmlelement)
-    for (at, av) in xmlelement.attributes.items():
-      if at == "EventNotifier":
-        self.eventNotifier = int(av)
+    def __init__(self, xmlelement=None):
+        Node.__init__(self)
+        self.nodeClass = NODE_CLASS_OBJECT
+        self.eventNotifier = 0
+        if xmlelement:
+            self.parseXML(xmlelement)
+
+    def parseXML(self, xmlelement):
+        Node.parseXML(self, xmlelement)
+        for (at, av) in xmlelement.attributes.items():
+            if at == "EventNotifier":
+                self.eventNotifier = int(av)
 
 class VariableNode(Node):
-  def __init__(self, xmlelement = None):
-    Node.__init__(self)
-    self.nodeClass = NODE_CLASS_VARIABLE
-    self.dataType            = NodeId()
-    self.valueRank           = -1
-    self.arrayDimensions     = []
-    self.accessLevel         = 0
-    self.userAccessLevel     = 0
-    self.minimumSamplingInterval = 0.0
-    self.historizing         = False
-    self.value               = None
-    self.xmlValueDef         = None
-    if xmlelement:
-      self.parseXML(xmlelement)
-
-  def parseXML(self, xmlelement):
-    Node.parseXML(self, xmlelement)
-    for (at, av) in xmlelement.attributes.items():
-      if at == "ValueRank":
-        self.valueRank = int(av)
-      elif at == "AccessLevel":
-        self.accessLevel = int(av)
-      elif at == "UserAccessLevel":
-        self.userAccessLevel = int(av)
-      elif at == "MinimumSamplingInterval":
-        self.minimumSamplingInterval = float(av)
-      elif at == "DataType":
-        if "=" in av:
-          self.dataType = NodeId(av)
+    def __init__(self, xmlelement=None):
+        Node.__init__(self)
+        self.nodeClass = NODE_CLASS_VARIABLE
+        self.dataType = NodeId()
+        self.valueRank = -1
+        self.arrayDimensions = []
+        # Set access levels to read by default
+        self.accessLevel = 1
+        self.userAccessLevel = 1
+        self.minimumSamplingInterval = 0.0
+        self.historizing = False
+        self.value = None
+        self.xmlValueDef = None
+        if xmlelement:
+            self.parseXML(xmlelement)
+
+    def parseXML(self, xmlelement):
+        Node.parseXML(self, xmlelement)
+        for (at, av) in xmlelement.attributes.items():
+            if at == "ValueRank":
+                self.valueRank = int(av)
+            elif at == "AccessLevel":
+                self.accessLevel = int(av)
+            elif at == "UserAccessLevel":
+                self.userAccessLevel = int(av)
+            elif at == "MinimumSamplingInterval":
+                self.minimumSamplingInterval = float(av)
+            elif at == "DataType":
+                if "=" in av:
+                    self.dataType = NodeId(av)
+                else:
+                    self.dataType = av
+
+        for x in xmlelement.childNodes:
+            if x.nodeType != x.ELEMENT_NODE:
+                continue
+            if x.localName == "Value":
+                self.xmlValueDef = x
+            elif x.localName == "DataType":
+                self.dataType = NodeId(str(x))
+            elif x.localName == "ValueRank":
+                self.valueRank = int(unicode(x.firstChild.data))
+            elif x.localName == "ArrayDimensions":
+                self.arrayDimensions = int(unicode(x.firstChild.data))
+            elif x.localName == "AccessLevel":
+                self.accessLevel = int(unicode(x.firstChild.data))
+            elif x.localName == "UserAccessLevel":
+                self.userAccessLevel = int(unicode(x.firstChild.data))
+            elif x.localName == "MinimumSamplingInterval":
+                self.minimumSamplingInterval = float(unicode(x.firstChild.data))
+            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
+
+        # FIXME: Don't build at all or allocate "defaults"? I'm for not building at all.
+        if self.xmlValueDef == 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)
+
+        # Array Dimensions must accurately represent the value and will be patched
+        # reflect the exaxt dimensions attached binary stream.
+        if not isinstance(self.value, Value) or len(self.value.value) == 0:
+            self.arrayDimensions = []
         else:
-          self.dataType = av
-
-    for x in xmlelement.childNodes:
-      if x.nodeType != x.ELEMENT_NODE:
-        continue
-      if x.tagName == "Value":
-          self.__xmlValueDef__ = x
-      elif x.tagName == "DataType":
-          self.dataType = NodeId(str(x))
-      elif x.tagName == "ValueRank":
-          self.valueRank = int(unicode(x.firstChild.data))
-      elif x.tagName == "ArrayDimensions":
-          self.arrayDimensions = int(unicode(x.firstChild.data))
-      elif x.tagName == "AccessLevel":
-          self.accessLevel = int(unicode(x.firstChild.data))
-      elif x.tagName == "UserAccessLevel":
-          self.userAccessLevel = int(unicode(x.firstChild.data))
-      elif x.tagName == "MinimumSamplingInterval":
-          self.minimumSamplingInterval = float(unicode(x.firstChild.data))
-      elif x.tagName == "Historizing":
-          self.historizing = "false" not in x.lower()
+            # Parser only permits 1-d arrays, which means we do not have to check further dimensions
+            self.arrayDimensions = [len(self.value.value)]
+        return True
+
 
 class VariableTypeNode(VariableNode):
-  def __init__(self, xmlelement = None):
-    VariableNode.__init__(self)
-    self.nodeClass = NODE_CLASS_VARIABLETYPE
-    if xmlelement:
-      self.parseXML(xmlelement)
+    def __init__(self, xmlelement=None):
+        VariableNode.__init__(self)
+        self.nodeClass = NODE_CLASS_VARIABLETYPE
+        if xmlelement:
+            self.parseXML(xmlelement)
 
 class MethodNode(Node):
-  def __init__(self, xmlelement = None):
-    Node.__init__(self)
-    self.nodeClass = NODE_CLASS_METHOD
-    self.executable     = True
-    self.userExecutable = True
-    self.methodDecalaration = None
-    if xmlelement:
-      self.parseXML(xmlelement)
-
-  def parseXML(self, xmlelement):
-    Node.parseXML(self, xmlelement)
-    for (at, av) in xmlelement.attributes.items():
-      if at == "Executable":
-        self.executable = "false" not in av.lower()
-      if at == "UserExecutable":
-        self.userExecutable = "false" not in av.lower()
-      if at == "MethodDeclarationId":
-        self.methodDeclaration = str(av)
+    def __init__(self, xmlelement=None):
+        Node.__init__(self)
+        self.nodeClass = NODE_CLASS_METHOD
+        self.executable = True
+        self.userExecutable = True
+        self.methodDecalaration = None
+        if xmlelement:
+            self.parseXML(xmlelement)
+
+    def parseXML(self, xmlelement):
+        Node.parseXML(self, xmlelement)
+        for (at, av) in xmlelement.attributes.items():
+            if at == "Executable":
+                self.executable = "false" not in av.lower()
+            if at == "UserExecutable":
+                self.userExecutable = "false" not in av.lower()
+            if at == "MethodDeclarationId":
+                self.methodDeclaration = str(av)
 
 class ObjectTypeNode(Node):
-  def __init__(self, xmlelement = None):
-    Node.__init__(self)
-    self.nodeClass = NODE_CLASS_OBJECTTYPE
-    self.isAbstract = False
-    if xmlelement:
-      self.parseXML(xmlelement)
-
-  def parseXML(self, xmlelement):
-    Node.parseXML(self, xmlelement)
-    for (at, av) in xmlelement.attributes.items():
-      if at == "IsAbstract":
-        self.isAbstract = "false" not in av.lower()
+    def __init__(self, xmlelement=None):
+        Node.__init__(self)
+        self.nodeClass = NODE_CLASS_OBJECTTYPE
+        self.isAbstract = False
+        if xmlelement:
+            self.parseXML(xmlelement)
+
+    def parseXML(self, xmlelement):
+        Node.parseXML(self, xmlelement)
+        for (at, av) in xmlelement.attributes.items():
+            if at == "IsAbstract":
+                self.isAbstract = "false" not in av.lower()
 
 class DataTypeNode(Node):
-  def __init__(self, xmlelement = None):
-    Node.__init__(self)
-    self.nodeClass = NODE_CLASS_DATATYPE
-    self.isAbstract = False
-    if xmlelement:
-      self.parseXML(xmlelement)
-
-  def parseXML(self, xmlelement):
-    Node.parseXML(self, xmlelement)
-    for (at, av) in xmlelement.attributes.items():
-      if at == "IsAbstract":
-        self.isAbstract = "false" not in av.lower()
+    """ 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
+           a 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().
+    """
+    __isEnum__     = False
+    __xmlDefinition__ = None
+    __baseTypeEncoding__ = []
+    __encodable__ = False
+    __encodingBuilt__ = False
+    __definition__ = []
+
+    def __init__(self, xmlelement=None):
+        Node.__init__(self)
+        self.nodeClass = NODE_CLASS_DATATYPE
+        self.isAbstract = False
+        self.__xmlDefinition__ = None
+        self.__baseTypeEncoding__ = []
+        self.__encodable__ = None
+        self.__encodingBuilt__ = False
+        self.__definition__ = []
+        self.__isEnum__     = False
+        if xmlelement:
+            self.parseXML(xmlelement)
+
+    def parseXML(self, xmlelement):
+        Node.parseXML(self, xmlelement)
+        for (at, av) in xmlelement.attributes.items():
+            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, this function will call buildEncoding()
+            and return True if it succeeds.
+        """
+        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, this function will call buildEncoding()
+            and return the encoding if buildEncoding() succeeds.
+
+            If buildEncoding() fails or has failed, an empty list will be returned.
+        """
+        if self.__encodable__ == False:
+            if self.__encodingBuilt__ == False:
+                return self.buildEncoding()
+            return []
+        else:
+            return self.__baseTypeEncoding__
+
+
+    def buildEncoding(self, nodeset, indent=0, force=False):
+        """ 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 successfull, 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==True:
+            self.__encodingBuilt__ = False
+
+        if self.__encodingBuilt__ == True:
+            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.__encodingBuilt__ = True # signify that we have attempted to built this type
+        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__
+
+        if self.__xmlDefinition__ == None:
+            # Check if there is a supertype available
+            for ref in self.inverseReferences:
+                # hasSubtype
+                if ref.referenceType.i == 45:
+                    targetNode = nodeset.nodes[ref.target]
+                    if targetNode is not None and isinstance(targetNode, DataTypeNode):
+                        logger.debug( prefix + "Attempting definition using supertype " + str(targetNode.browseName) + " for DataType " + " " + str(self.browseName))
+                        subenc = targetNode.buildEncoding(nodeset=nodeset, indent=indent+1)
+                        if not targetNode.isEncodable():
+                            self.__encodable__ = False
+                            break
+                        else:
+                            self.__baseTypeEncoding__ = self.__baseTypeEncoding__ + [self.browseName.name, subenc, 0]
+            if len(self.__baseTypeEncoding__) == 0:
+                logger.debug(prefix + "No viable definition for " + str(self.browseName) + " " + str(self.id) + " found.")
+                self.__encodable__ = False
+
+            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 = True
+        isSubType = True
+        hasValueRank = 0
+
+        # We need to store the definition as ordered data, but can't 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 = 0
+                for at,av in x.attributes.items():
+                    if at == "DataType":
+                        fdtype = str(av)
+                        isEnum = False
+                    elif at == "Name":
+                        fname = str(av)
+                    elif at == "Value":
+                        enumVal = int(av)
+                        isSubType = False
+                    elif at == "ValueRank":
+                        valueRank = int(av)
+                        if valueRank > 0:
+                            logger.warn("Value ranks >0 not fully supported. Further steps may fail")
+                    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 == isSubType:
+                    # This is an error
+                    logger.warn("DataType contains both enumeration and subtype (or neither)")
+                    self.__encodable__ = False
+                    break
+                elif isEnum:
+                    # This is an enumeration
+                    enumDict.append((fname, enumVal))
+                    continue
+                else:
+                    # This might be a subtype... follow the node defined as datatype to find out
+                    # what encoding to use
+                    dtnode = nodeset.nodes[NodeId(fdtype)]
+                    # 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)
+                    self.__baseTypeEncoding__ = self.__baseTypeEncoding__ + [[fname, subenc, valueRank]]
+                    if not dtnode.isEncodable():
+                        # If we inherit an encoding from an unencodable not, this node is
+                        # also not encodable
+                        self.__encodable__ = False
+                        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 isEnum == 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
+                self.__definition__ = typeDict
+            logger.debug( "")
+        return self.__baseTypeEncoding__
 
 class ViewNode(Node):
-  def __init__(self, xmlelement = None):
-    Node.__init__(self)
-    self.nodeClass = NODE_CLASS_VIEW
-    self.containsNoLoops == False
-    self.eventNotifier == False
-    if xmlelement:
-      self.parseXML(xmlelement)
-
-  def parseXML(self, xmlelement):
-    Node.parseXML(self, xmlelement)
-    for (at, av) in xmlelement.attributes.items():
-      if at == "ContainsNoLoops":
-        self.containsNoLoops = "false" not in av.lower()
-      if at == "eventNotifier":
-        self.eventNotifier = "false" not in av.lower()
+    def __init__(self, xmlelement=None):
+        Node.__init__(self)
+        self.nodeClass = NODE_CLASS_VIEW
+        self.containsNoLoops == False
+        self.eventNotifier == False
+        if xmlelement:
+            self.parseXML(xmlelement)
+
+    def parseXML(self, xmlelement):
+        Node.parseXML(self, xmlelement)
+        for (at, av) in xmlelement.attributes.items():
+            if at == "ContainsNoLoops":
+                self.containsNoLoops = "false" not in av.lower()
+            if at == "eventNotifier":
+                self.eventNotifier = "false" not in av.lower()

+ 260 - 154
tools/nodeset_compiler/nodeset.py

@@ -21,23 +21,27 @@ import sys
 import xml.dom.minidom as dom
 from struct import pack as structpack
 from time import struct_time, strftime, strptime, mktime
-import logging; logger = logging.getLogger(__name__)
+import logging;
+
+logger = logging.getLogger(__name__)
 
-from datatypes import *
 from nodes import *
-from constants import *
+from opaque_type_mapping import opaque_type_mapping
 
 ####################
 # Helper Functions #
 ####################
 
 hassubtype = NodeId("ns=0;i=45")
-def getSubTypesOf(nodeset, node):
-  re = [node]
-  for ref in node.references: 
-    if ref.referenceType == hassubtype and ref.isForward:
-      re = re + getSubTypesOf(nodeset, nodeset.nodes[ref.target])
-  return re
+
+def getSubTypesOf(nodeset, node, skipNodes=[]):
+    if node in skipNodes:
+        return []
+    re = [node]
+    for ref in node.references:
+        if ref.referenceType == hassubtype and ref.isForward:
+            re = re + getSubTypesOf(nodeset, nodeset.nodes[ref.target], skipNodes=skipNodes)
+    return re
 
 def extractNamespaces(xmlfile):
     # Extract a list of namespaces used. The first namespace is always
@@ -46,30 +50,30 @@ def extractNamespaces(xmlfile):
     # access this dom2 <uri></uri> elements (only attribute xmlns= are accessible
     # using minidom). We need them for dereferencing though... This function
     # attempts to do just that.
-    
+
     namespaces = ["http://opcfoundation.org/UA/"]
     infile = open(xmlfile.name)
     foundURIs = False
     nsline = ""
     line = infile.readline()
     for line in infile:
-      if "<namespaceuris>" in line.lower():
-        foundURIs = True
-      elif "</namespaceuris>" in line.lower():
-        foundURIs = False
-        nsline = nsline + line
-        break
-      if foundURIs:
-        nsline = nsline + line
+        if "<namespaceuris>" in line.lower():
+            foundURIs = True
+        elif "</namespaceuris>" in line.lower():
+            foundURIs = False
+            nsline = nsline + line
+            break
+        if foundURIs:
+            nsline = nsline + line
 
     if len(nsline) > 0:
-      ns = dom.parseString(nsline).getElementsByTagName("NamespaceUris")
-      for uri in ns[0].childNodes:
-        if uri.nodeType != uri.ELEMENT_NODE:
-          continue
-        if uri.firstChild.data in namespaces:
-          continue
-        namespaces.append(uri.firstChild.data)
+        ns = dom.parseString(nsline).getElementsByTagName("NamespaceUris")
+        for uri in ns[0].childNodes:
+            if uri.nodeType != uri.ELEMENT_NODE:
+                continue
+            if uri.firstChild.data in namespaces:
+                continue
+            namespaces.append(uri.firstChild.data)
     infile.close()
     return namespaces
 
@@ -79,140 +83,242 @@ def buildAliasList(xmlelement):
        dereferencing during pointer linkage (see linkOpenPointer())."""
     aliases = {}
     for al in xmlelement.childNodes:
-      if al.nodeType == al.ELEMENT_NODE:
-        if al.hasAttribute("Alias"):
-          aliasst = al.getAttribute("Alias")
-          aliasnd = unicode(al.firstChild.data)
-          aliases[aliasst] = aliasnd
+        if al.nodeType == al.ELEMENT_NODE:
+            if al.hasAttribute("Alias"):
+                aliasst = al.getAttribute("Alias")
+                aliasnd = unicode(al.firstChild.data)
+                aliases[aliasst] = aliasnd
     return aliases
 
 class NodeSet(object):
-  """ This class handles parsing XML description of namespaces, instantiating
-      nodes, linking references, graphing the namespace and compiling a binary
-      representation.
-
-      Note that nodes assigned to this class are not restricted to having a
-      single namespace ID. This class represents the entire physical address
-      space of the binary representation and all nodes that are to be included
-      in that segment of memory.
-  """
-  def __init__(self):
-    self.nodes = {}
-    self.namespaces = ["http://opcfoundation.org/UA/"]
-
-  def sanitize(self):
-    for n in self.nodes.values():
-      if n.sanitize() == False:
-        raise Exception("Failed to sanitize node " + str(n))
-
-    # Sanitize reference consistency
-    for n in self.nodes.values():
-      for ref in n.references:
-        if not ref.source == n.id:
-          raise Exception("Reference " + str(ref) + " has an invalid source")
-        if not ref.referenceType in self.nodes:
-          raise Exception("Reference " + str(ref) + " has an unknown reference type")
-        if not ref.target in self.nodes:
-          raise Exception("Reference " + str(ref) + " has an unknown target")
-
-  def addNamespace(self, nsURL):
-    if not nsURL in self.namespaces:
-      self.namespaces.append(nsURL)
-
-  def createNamespaceMapping(self, orig_namespaces):
-    """Creates a dict that maps from the nsindex in the original nodeset to the
-       nsindex in the combined nodeset"""
-    m = {}
-    for index,name in enumerate(orig_namespaces):
-      m[index] = self.namespaces.index(name)
-    return m
-
-  def getNodeByBrowseName(self, idstring):
-    return next((n for n in self.nodes.values() if idstring==n.browseName.name), None)
-
-  def getRoot(self):
-    return self.getNodeByBrowseName("Root")
-
-  def createNode(self, xmlelement, nsMapping, hidden=False):
-    ndtype = xmlelement.tagName.lower()
-    if ndtype[:2] == "ua":
-      ndtype = ndtype[2:]
-
-    node = None
-    if ndtype == 'variable':
-      node = VariableNode(xmlelement)
-    if ndtype == 'object':
-      node = ObjectNode(xmlelement)
-    if ndtype == 'method':
-      node = MethodNode(xmlelement)
-    if ndtype == 'objecttype':
-      node = ObjectTypeNode(xmlelement)
-    if ndtype == 'variabletype':
-      node = VariableTypeNode(xmlelement)
-    if ndtype == 'methodtype':
-      node = MethodNode(xmlelement)
-    if ndtype == 'datatype':
-      node = DataTypeNode(xmlelement)
-    if ndtype == 'referencetype':
-      node = ReferenceTypeNode(xmlelement)
-
-    if node and hidden:
-        node.hidden = True
+    """ This class handles parsing XML description of namespaces, instantiating
+        nodes, linking references, graphing the namespace and compiling a binary
+        representation.
+
+        Note that nodes assigned to this class are not restricted to having a
+        single namespace ID. This class represents the entire physical address
+        space of the binary representation and all nodes that are to be included
+        in that segment of memory.
+    """
+
+    def __init__(self):
+        self.nodes = {}
+        self.aliases = {}
+        self.namespaces = ["http://opcfoundation.org/UA/"]
+
+    def sanitize(self):
+        for n in self.nodes.values():
+            if n.sanitize() == False:
+                raise Exception("Failed to sanitize node " + str(n))
+
+        # Sanitize reference consistency
+        for n in self.nodes.values():
+            for ref in n.references:
+                if not ref.source == n.id:
+                    raise Exception("Reference " + str(ref) + " has an invalid source")
+                if not ref.referenceType in self.nodes:
+                    raise Exception("Reference " + str(ref) + " has an unknown reference type")
+                if not ref.target in self.nodes:
+                    raise Exception("Reference " + str(ref) + " has an unknown target")
+
+    def addNamespace(self, nsURL):
+        if not nsURL in self.namespaces:
+            self.namespaces.append(nsURL)
+
+    def createNamespaceMapping(self, orig_namespaces):
+        """Creates a dict that maps from the nsindex in the original nodeset to the
+           nsindex in the combined nodeset"""
+        m = {}
+        for index, name in enumerate(orig_namespaces):
+            m[index] = self.namespaces.index(name)
+        return m
+
+    def getNodeByBrowseName(self, idstring):
+        return next((n for n in self.nodes.values() if idstring == n.browseName.name), None)
+
+    def getNodeById(self, namespace, id):
+        nodeId = NodeId()
+        nodeId.ns = namespace
+        nodeId.i = id
+        return self.nodes[nodeId]
+
+    def getRoot(self):
+        return self.getNodeByBrowseName("Root")
+
+    def createNode(self, xmlelement, nsMapping, hidden=False):
+        ndtype = xmlelement.localName.lower()
+        if ndtype[:2] == "ua":
+            ndtype = ndtype[2:]
+
+        node = None
+        if ndtype == 'variable':
+            node = VariableNode(xmlelement)
+        if ndtype == 'object':
+            node = ObjectNode(xmlelement)
+        if ndtype == 'method':
+            node = MethodNode(xmlelement)
+        if ndtype == 'objecttype':
+            node = ObjectTypeNode(xmlelement)
+        if ndtype == 'variabletype':
+            node = VariableTypeNode(xmlelement)
+        if ndtype == 'methodtype':
+            node = MethodNode(xmlelement)
+        if ndtype == 'datatype':
+            node = DataTypeNode(xmlelement)
+        if ndtype == 'referencetype':
+            node = ReferenceTypeNode(xmlelement)
+
+        if node and hidden:
+            node.hidden = True
+            # References from an existing nodeset are all suppressed
+            for ref in node.references:
+                ref.hidden = True
+            for ref in node.inverseReferences:
+                ref.hidden = True
+        return node
+
+    def hide_node(self, nodeId, hidden=True):
+        if not nodeId in self.nodes:
+            return False
+        node = self.nodes[nodeId]
+        node.hidden = hidden
         # References from an existing nodeset are all suppressed
         for ref in node.references:
-            ref.hidden = True
+            ref.hidden = hidden
         for ref in node.inverseReferences:
-            ref.hidden = True
-    return node
-
-  def addNodeSet(self, xmlfile, hidden = False):
-    # Extract NodeSet DOM
-    nodesets = dom.parse(xmlfile).getElementsByTagName("UANodeSet")
-    if len(nodesets) == 0 or len(nodesets) > 1:
-      raise Exception(self, self.originXML + " contains no or more then 1 nodeset")
-    nodeset = nodesets[0]
-
-    # Create the namespace mapping
-    orig_namespaces = extractNamespaces(xmlfile) # List of namespaces used in the xml file
-    for ns in orig_namespaces:
-        self.addNamespace(ns)
-    nsMapping = self.createNamespaceMapping(orig_namespaces)
-
-    # Extract the aliases
-    aliases = None
-    for nd in nodeset.childNodes:
-      if nd.nodeType != nd.ELEMENT_NODE:
-        continue
-      ndtype = nd.tagName.lower()
-      if 'aliases' in ndtype:
-        aliases = buildAliasList(nd)
-
-    # Instantiate nodes
-    newnodes = []
-    for nd in nodeset.childNodes:
-      if nd.nodeType != nd.ELEMENT_NODE:
-        continue
-      node = self.createNode(nd, nsMapping, hidden)
-      if not node:
-        continue
-      node.replaceAliases(aliases)
-      node.replaceNamespaces(nsMapping)
-      
-      # Add the node the the global dict
-      if node.id in self.nodes:
-        raise Exception("XMLElement with duplicate ID " + str(node.id))
-      self.nodes[node.id] = node
-      newnodes.append(node)
-
-    # add inverse references
-    for node in newnodes:
+            ref.hidden = hidden
+        return True
+
+    def merge_dicts(self, *dict_args):
+        """
+        Given any number of dicts, shallow copy and merge into a new dict,
+        precedence goes to key value pairs in latter dicts.
+        """
+        result = {}
+        for dictionary in dict_args:
+            result.update(dictionary)
+        return result
+
+    def addNodeSet(self, xmlfile, hidden=False, typesArray="UA_TYPES"):
+        # Extract NodeSet DOM
+        nodesets = dom.parse(xmlfile).getElementsByTagName("UANodeSet")
+        if len(nodesets) == 0 or len(nodesets) > 1:
+            raise Exception(self, self.originXML + " contains no or more then 1 nodeset")
+        nodeset = nodesets[0]
+
+        # Create the namespace mapping
+        orig_namespaces = extractNamespaces(xmlfile)  # List of namespaces used in the xml file
+        for ns in orig_namespaces:
+            self.addNamespace(ns)
+        nsMapping = self.createNamespaceMapping(orig_namespaces)
+
+        # Extract the aliases
+        for nd in nodeset.childNodes:
+            if nd.nodeType != nd.ELEMENT_NODE:
+                continue
+            ndtype = nd.localName.lower()
+            if 'aliases' in ndtype:
+                self.aliases = self.merge_dicts(self.aliases, buildAliasList(nd))
+
+        # Instantiate nodes
+        newnodes = []
+        for nd in nodeset.childNodes:
+            if nd.nodeType != nd.ELEMENT_NODE:
+                continue
+            node = self.createNode(nd, nsMapping, hidden)
+            if not node:
+                continue
+            node.replaceAliases(self.aliases)
+            node.replaceNamespaces(nsMapping)
+            node.typesArray = typesArray
+
+            # Add the node the the global dict
+            if node.id in self.nodes:
+                raise Exception("XMLElement with duplicate ID " + str(node.id))
+            self.nodes[node.id] = node
+            newnodes.append(node)
+
+        # add inverse references
+        for node in newnodes:
+            for ref in node.references:
+                newsource = self.nodes[ref.target]
+                hide = ref.hidden or (node.hidden and newsource.hidden)
+                newref = Reference(newsource.id, ref.referenceType, ref.source, False, hide, inferred=True)
+                newsource.inverseReferences.add(newref)
+            for ref in node.inverseReferences:
+                newsource = self.nodes[ref.target]
+                hide = ref.hidden or (node.hidden and newsource.hidden)
+                newref = Reference(newsource.id, ref.referenceType, ref.source, True, hide, inferred=True)
+                newsource.references.add(newref)
+
+    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]
+        refId = NodeId()
         for ref in node.references:
-            newsource = self.nodes[ref.target]
-            hide = ref.hidden or (node.hidden and newsource.hidden)
-            newref = Reference(newsource.id, ref.referenceType, ref.source, False, hide)
-            newsource.inverseReferences.add(newref)
+            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 buildEncodingRules(self):
+        """ Calls buildEncoding() for all DataType nodes (opcua_node_dataType_t).
+
+            No return value
+        """
+        stat = {True: 0, False: 0}
+        for n in self.nodes.values():
+            if isinstance(n, DataTypeNode):
+                n.buildEncoding(self)
+                stat[n.isEncodable()] = stat[n.isEncodable()] + 1
+        logger.debug("Type definitions built/passed: " +  str(stat))
+
+
+    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
+        if node.browseName.name not in opaque_type_mapping:
+            return node
         for ref in node.inverseReferences:
-            newsource = self.nodes[ref.target]
-            hide = ref.hidden or (node.hidden and newsource.hidden)
-            newref = Reference(newsource.id, ref.referenceType, ref.source, True, hide)
-            newsource.references.add(newref)
+            if ref.referenceType.i == 45:
+                return self.getBaseDataType(self.nodes[ref.target])
+        return node
+                
+    def getDataTypeNode(self, dataType):
+        if isinstance(dataType, basestring):
+            if not valueIsInternalType(dataType):
+                logger.error("Not a valid dataType string: " + dataType)
+                return None
+            return self.nodes[NodeId(self.aliases[dataType])]
+        if isinstance(dataType, NodeId):
+            if dataType.i == 0:
+                return None
+            dataTypeNode = self.nodes[dataType]
+            if not isinstance(dataTypeNode, DataTypeNode):
+                logger.error("Node id " + str(dataType) + " is not reference a valid dataType.")
+                return None
+            if not dataTypeNode.isEncodable():
+                logger.warn("DataType " + str(dataTypeNode.browseName) + " is not encodable.")
+            return dataTypeNode
+        return None
+
+    def getRelevantOrderingReferences(self):
+        relevant_types = getSubTypesOf(self,
+                                       self.getNodeByBrowseName("HierarchicalReferences"),
+                                       [])
+        relevant_types += getSubTypesOf(self,
+                                        self.getNodeByBrowseName("HasEncoding"),
+                                        [])
+        relevant_types = map(lambda x: x.id, relevant_types)
+        return relevant_types

+ 86 - 31
tools/nodeset_compiler/nodeset_compiler.py

@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
 # This Source Code Form is subject to the terms of the Mozilla Public
@@ -26,7 +26,7 @@ from nodeset import *
 from backend_open62541 import generateOpen62541Code
 
 parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter)
-parser.add_argument('-e','--existing',
+parser.add_argument('-e', '--existing',
                     metavar="<existingNodeSetXML>",
                     type=argparse.FileType('r'),
                     dest="existing",
@@ -34,10 +34,11 @@ parser.add_argument('-e','--existing',
                     default=[],
                     help='NodeSet XML files with nodes that are already present on the server.')
 
-parser.add_argument('infiles',
+parser.add_argument('-x', '--xml',
                     metavar="<nodeSetXML>",
-                    action='append',
                     type=argparse.FileType('r'),
+                    action='append',
+                    dest="infiles",
                     default=[],
                     help='NodeSet XML files with nodes that shall be generated.')
 
@@ -50,7 +51,7 @@ parser.add_argument('--generate-ns0',
                     dest="generate_ns0",
                     help='Omit some consistency checks for bootstrapping namespace 0, create references to parents and type definitions manually')
 
-parser.add_argument('-b','--blacklist',
+parser.add_argument('-b', '--blacklist',
                     metavar="<blacklistFile>",
                     type=argparse.FileType('r'),
                     action='append',
@@ -58,15 +59,32 @@ parser.add_argument('-b','--blacklist',
                     default=[],
                     help='Loads a list of NodeIDs stored in blacklistFile (one NodeID per line). Any of the nodeIds encountered in this file will be removed from the nodeset prior to compilation. Any references to these nodes will also be removed')
 
-parser.add_argument('-s','--suppress',
+parser.add_argument('-i', '--ignore',
+                    metavar="<ignoreFile>",
+                    type=argparse.FileType('r'),
+                    action='append',
+                    dest="ignoreFiles",
+                    default=[],
+                    help='Loads a list of NodeIDs stored in ignoreFile (one NodeID per line). Any of the nodeIds encountered in this file will be kept in the nodestore but not printed in the generated code')
+
+parser.add_argument('-s', '--suppress',
                     metavar="<attribute>",
                     action='append',
                     dest="suppressedAttributes",
-                    choices=['description', 'browseName', 'displayName', 'writeMask', 'userWriteMask','nodeid'],
+                    choices=['description', 'browseName', 'displayName', 'writeMask', 'userWriteMask', 'nodeid'],
                     default=[],
                     help="Suppresses the generation of some node attributes. Currently supported options are 'description', 'browseName', 'displayName', 'writeMask', 'userWriteMask' and 'nodeid'.")
 
-parser.add_argument('-v','--verbose', action='count', help='Make the script more verbose. Can be applied up to 4 times')
+parser.add_argument('-t', '--types-array',
+                    metavar="<typesArray>",
+                    action='append',
+                    type=str,
+                    dest="typesArray",
+                    default=[],
+                    help='Types array for the given namespace. Can be used mutliple times to define (in the same order as the .xml files, first for --existing, then --xml) the type arrays')
+
+parser.add_argument('-v', '--verbose', action='count',
+                    help='Make the script more verbose. Can be applied up to 4 times')
 
 args = parser.parse_args()
 
@@ -75,27 +93,37 @@ logger = logging.getLogger(__name__)
 logger.setLevel(logging.INFO)
 verbosity = 0
 if args.verbose:
-  verbosity = int(args.verbose)
-if (verbosity==1):
-  logging.basicConfig(level=logging.ERROR)
-elif (verbosity==2):
-  logging.basicConfig(level=logging.WARNING)
-elif (verbosity==3):
-  logging.basicConfig(level=logging.INFO)
-elif (verbosity>=4):
-  logging.basicConfig(level=logging.DEBUG)
+    verbosity = int(args.verbose)
+if (verbosity == 1):
+    logging.basicConfig(level=logging.ERROR)
+elif (verbosity == 2):
+    logging.basicConfig(level=logging.WARNING)
+elif (verbosity == 3):
+    logging.basicConfig(level=logging.INFO)
+elif (verbosity >= 4):
+    logging.basicConfig(level=logging.DEBUG)
 else:
-  logging.basicConfig(level=logging.CRITICAL)
+    logging.basicConfig(level=logging.CRITICAL)
 
 # Create a new nodeset. The nodeset name is not significant.
 # Parse the XML files
 ns = NodeSet()
+nsCount = 0
+
+def getTypesArray(nsIdx):
+    if nsIdx < len(args.typesArray):
+        return args.typesArray[nsIdx]
+    else:
+        return "UA_TYPES"
+
 for xmlfile in args.existing:
-  logger.info("Preprocessing (existing) " + str(xmlfile.name))
-  ns.addNodeSet(xmlfile, True)
+    logger.info("Preprocessing (existing) " + str(xmlfile.name))
+    ns.addNodeSet(xmlfile, True, typesArray=getTypesArray(nsCount))
+    nsCount +=1
 for xmlfile in args.infiles:
-  logger.info("Preprocessing " + str(xmlfile.name))
-  ns.addNodeSet(xmlfile)
+    logger.info("Preprocessing " + str(xmlfile.name))
+    ns.addNodeSet(xmlfile, typesArray=getTypesArray(nsCount))
+    nsCount +=1
 
 # # We need to notify the open62541 server of the namespaces used to be able to use i.e. ns=3
 # namespaceArrayNames = preProc.getUsedNamespaceArrayNames()
@@ -106,20 +134,47 @@ for xmlfile in args.infiles:
 # Doing this now ensures that unlinkable pointers will be cleanly removed
 # during sanitation.
 for blacklist in args.blacklistFiles:
-  for line in blacklist.readlines():
-    line = line.replace(" ","")
-    id = line.replace("\n","")
-    if ns.getNodeByIDString(id) == None:
-      logger.info("Can't blacklist node, namespace does currently not contain a node with id " + str(id))
-    else:
-      ns.removeNodeById(line)
-  blacklist.close()
+    for line in blacklist.readlines():
+        line = line.replace(" ", "")
+        id = line.replace("\n", "")
+        if ns.getNodeByIDString(id) == None:
+            logger.info("Can't blacklist node, namespace does currently not contain a node with id " + str(id))
+        else:
+            ns.removeNodeById(line)
+    blacklist.close()
+
+# Set the nodes from the ignore list to hidden. This removes them from dependency calculation
+# and from printing their generated code.
+# These nodes should be already pre-created on the server to avoid any errors during
+# creation.
+for ignoreFile in args.ignoreFiles:
+    for line in ignoreFile.readlines():
+        line = line.replace(" ", "")
+        id = line.replace("\n", "")
+        ns.hide_node(NodeId(id))
+        #if not ns.hide_node(NodeId(id)):
+        #    logger.info("Can't ignore node, namespace does currently not contain a node with id " + str(id))
+    ignoreFile.close()
 
 # Remove nodes that are not printable or contain parsing errors, such as
 # unresolvable or no references or invalid NodeIDs
 ns.sanitize()
 
+
+# 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
+ns.buildEncodingRules()
+
+# Allocate/Parse the data values. In order to do this, we must have run
+# buidEncodingRules.
+ns.allocateVariables()
+
+#printDependencyGraph(ns)
+
 # Create the C code with the open62541 backend of the compiler
 logger.info("Generating Code")
-generateOpen62541Code(ns, args.outputFile, args.suppressedAttributes, args.generate_ns0)
+generateOpen62541Code(ns, args.outputFile, args.suppressedAttributes, args.generate_ns0, args.typesArray)
 logger.info("NodeSet generation code successfully printed")

+ 57 - 55
tools/nodeset_compiler/nodeset_testing.py

@@ -1,73 +1,75 @@
+#!/usr/bin/env python
+
 from nodeset import *
 
 class testing:
-  def __init__(self):
-    self.ns = NodeSet("testing")
+    def __init__(self):
+        self.ns = NodeSet("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 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 2: Linking address space references and datatypes")
+        self.ns.linkOpenPointers()
+        self.ns.sanitize()
 
-    logger.debug("Phase 3: Comprehending DataType encoding rules")
-    self.ns.buildEncodingRules()
+        logger.debug("Phase 3: Comprehending DataType encoding rules")
+        self.ns.buildEncodingRules()
 
-    logger.debug("Phase 4: Allocating variable value data")
-    self.ns.allocateVariables()
+        logger.debug("Phase 4: Allocating variable value data")
+        self.ns.allocateVariables()
 
-    bin = self.ns.buildBinary()
-    f = open("binary.base64","w+")
-    f.write(bin.encode("base64"))
-    f.close()
+        bin = self.ns.buildBinary()
+        f = open("binary.base64", "w+")
+        f.write(bin.encode("base64"))
+        f.close()
 
-    allnodes = self.ns.nodes;
-    ns = [self.ns.getRoot()]
+        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 (not r.target() 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")
+        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 (not r.target() 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")
+    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 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 2: Linking address space references and datatypes")
+        self.ns.linkOpenPointers()
+        self.ns.sanitize()
 
-    logger.debug("Phase 3: Calling C Printers")
-    code = self.ns.printOpen62541Header()
+        logger.debug("Phase 3: Calling C Printers")
+        code = self.ns.printOpen62541Header()
 
-    codeout = open("./open62541_nodeset.c", "w+")
-    for line in code:
-      codeout.write(line + "\n")
-    codeout.close()
-    return
+        codeout = open("./open62541_nodeset.c", "w+")
+        for line in code:
+            codeout.write(line + "\n")
+        codeout.close()
+        return
 
 if __name__ == '__main__':
-  tst = testing_open62541_header()
+    tst = testing_open62541_header()

+ 10 - 0
tools/schema/datatypes_minimal.txt

@@ -184,3 +184,13 @@ SessionDiagnosticsDataType
 ServiceCounterDataType
 SessionSecurityDiagnosticsDataType
 Duration
+UtcTime
+LocaleId
+RedundancySupport
+RedundantServerDataType
+NetworkGroupDataType
+NumericRange
+EndpointUrlListDataType
+ModelChangeStructureDataType
+SemanticChangeStructureDataType
+TimeZoneDataType

Fichier diff supprimé car celui-ci est trop grand
+ 748 - 3786
tools/schema/namespace0/Opc.Ua.NodeSet2.Minimal.xml


+ 1 - 1
tools/travis/travis_linux_script.sh

@@ -85,7 +85,7 @@ else
     echo -e "\r\n== Full Namespace 0 Generation ==" && echo -en 'travis_fold:start:script.build.ns0\\r'
     mkdir -p build
     cd build
-    cmake -DCMAKE_BUILD_TYPE=Debug -DUA_ENABLE_GENERATE_NAMESPACE0=On -DUA_BUILD_EXAMPLES=ON  ..
+    cmake -DCMAKE_BUILD_TYPE=Debug -DUA_NAMESPACE0_XML=../tools/schema/namespace0/Opc.Ua.NodeSet2.xml -DUA_DATATYPES_FILE=../tools/schema/datatypes_full.txt -DUA_BUILD_EXAMPLES=ON  ..
     make -j
     if [ $? -ne 0 ] ; then exit 1 ; fi
     cd .. && rm build -rf

+ 1 - 1
tools/travis/travis_osx_script.sh

@@ -18,7 +18,7 @@ echo -en 'travis_fold:end:script.build.doc\\r'
 echo "Full Namespace 0 Generation"  && echo -en 'travis_fold:start:script.build.ns0\\r'
 mkdir -p build
 cd build
-cmake -DCMAKE_BUILD_TYPE=Debug -DUA_ENABLE_GENERATE_NAMESPACE0=On -DUA_BUILD_EXAMPLES=ON  ..
+cmake -DCMAKE_BUILD_TYPE=Debug -DUA_NAMESPACE0_XML=../tools/schema/namespace0/Opc.Ua.NodeSet2.xml -DUA_DATATYPES_FILE=../tools/schema/datatypes_full.txt -DUA_BUILD_EXAMPLES=ON  ..
 make -j
 cd .. && rm -rf build
 echo -en 'travis_fold:end:script.build.ns0\\r'