Sfoglia il codice sorgente

Merge branch '0.2'

Julius Pfrommer 7 anni fa
parent
commit
430b510c12

+ 3 - 5
CMakeLists.txt

@@ -105,7 +105,6 @@ endif()
 # 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_EXAMPLES_NODESET_COMPILER "Generate an OPC UA information model from a nodeset XML (experimental)" OFF)
 
 # Advanced Build Targets
@@ -435,6 +434,9 @@ endif()
 # Build Selected Targets #
 ##########################
 
+# always include, builds with make doc
+add_subdirectory(doc)
+
 if(UA_BUILD_EXAMPLES)
     add_subdirectory(examples)
 endif()
@@ -445,10 +447,6 @@ if(UA_BUILD_UNIT_TESTS)
     add_subdirectory(tests)
 endif()
 
-if(UA_BUILD_DOCUMENTATION)
-    add_subdirectory(doc)
-endif()
-
 if(UA_BUILD_EXAMPLES_NODESET_COMPILER)
   add_custom_target(generate_informationmodel ALL
                     DEPENDS ${PROJECT_BINARY_DIR}/src_generated/nodeset.h ${PROJECT_BINARY_DIR}/src_generated/nodeset.c)

+ 27 - 18
doc/CMakeLists.txt

@@ -1,4 +1,4 @@
-find_package(Sphinx REQUIRED)
+find_package(Sphinx)
 find_package(LATEX)
 
 set(DOC_LATEX_DIR ${PROJECT_BINARY_DIR}/doc_latex)
@@ -8,25 +8,15 @@ set(DOC_SRC_DIR   ${PROJECT_BINARY_DIR}/doc_src)
 make_directory(${DOC_SRC_DIR})
 file(GLOB DOC_SRC "${PROJECT_SOURCE_DIR}/doc/*")
 list(REMOVE_ITEM DOC_SRC "${PROJECT_SOURCE_DIR}/doc/conf.py")
-list(REMOVE_ITEM DOC_SRC "${PROJECT_SOURCE_DIR}/doc/tutorial_server_variables.rst")
-list(REMOVE_ITEM DOC_SRC "${PROJECT_SOURCE_DIR}/doc/tutorial_server_method.rst")
 file(COPY ${DOC_SRC} DESTINATION ${DOC_SRC_DIR})
 configure_file("${PROJECT_SOURCE_DIR}/doc/conf.py" "${DOC_SRC_DIR}/conf.py")
-configure_file("${PROJECT_SOURCE_DIR}/doc/tutorial_server_variables.rst"
-               "${DOC_SRC_DIR}/tutorial_server_variables.rst")
-configure_file("${PROJECT_SOURCE_DIR}/doc/tutorial_server_method.rst"
-               "${DOC_SRC_DIR}/tutorial_server_method.rst")
 
-# Copy example code
-set(EXAMPLES ${PROJECT_SOURCE_DIR}/examples/server_variable.c
-             ${PROJECT_SOURCE_DIR}/examples/server_method.c)
-file(COPY ${EXAMPLES} DESTINATION ${DOC_SRC_DIR})
-               
 function(generate_rst in out)
   add_custom_command(OUTPUT ${out} DEPENDS ${PROJECT_SOURCE_DIR}/tools/c2rst.py ${in}
                      PRE_BUILD COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/tools/c2rst.py ${in} ${out})
 endfunction()
 
+# Doc from headers
 generate_rst(${PROJECT_SOURCE_DIR}/include/ua_types.h ${DOC_SRC_DIR}/types.rst)
 generate_rst(${PROJECT_SOURCE_DIR}/include/ua_constants.h ${DOC_SRC_DIR}/constants.rst)
 generate_rst(${PROJECT_BINARY_DIR}/src_generated/ua_types_generated.h ${DOC_SRC_DIR}/types_generated.rst)
@@ -39,10 +29,15 @@ generate_rst(${PROJECT_SOURCE_DIR}/src/server/ua_services.h ${DOC_SRC_DIR}/servi
 generate_rst(${PROJECT_SOURCE_DIR}/src/server/ua_nodestore.h ${DOC_SRC_DIR}/nodestore.rst)
 generate_rst(${PROJECT_SOURCE_DIR}/src/server/ua_nodes.h ${DOC_SRC_DIR}/information_modelling.rst)
 
-generate_rst(${PROJECT_SOURCE_DIR}/examples/tutorials/client_firststeps.c
-             ${DOC_SRC_DIR}/tutorial_client_firststeps.rst)
-generate_rst(${PROJECT_SOURCE_DIR}/examples/tutorials/server_firststeps.c
-             ${DOC_SRC_DIR}/tutorial_server_firststeps.rst)
+# Doc from tutorial code
+generate_rst(${PROJECT_SOURCE_DIR}/examples/tutorial_datatypes.c ${DOC_SRC_DIR}/tutorial_datatypes.rst)
+generate_rst(${PROJECT_SOURCE_DIR}/examples/tutorial_server_firststeps.c ${DOC_SRC_DIR}/tutorial_server_firststeps.rst)
+generate_rst(${PROJECT_SOURCE_DIR}/examples/tutorial_server_variable.c ${DOC_SRC_DIR}/tutorial_server_variable.rst)
+generate_rst(${PROJECT_SOURCE_DIR}/examples/tutorial_server_variabletype.c ${DOC_SRC_DIR}/tutorial_server_variabletype.rst)
+generate_rst(${PROJECT_SOURCE_DIR}/examples/tutorial_server_datasource.c ${DOC_SRC_DIR}/tutorial_server_datasource.rst)
+generate_rst(${PROJECT_SOURCE_DIR}/examples/tutorial_server_object.c ${DOC_SRC_DIR}/tutorial_server_object.rst)
+generate_rst(${PROJECT_SOURCE_DIR}/examples/tutorial_server_method.c ${DOC_SRC_DIR}/tutorial_server_method.rst)
+generate_rst(${PROJECT_SOURCE_DIR}/examples/tutorial_client_firststeps.c ${DOC_SRC_DIR}/tutorial_client_firststeps.rst)
 
 add_custom_target(doc_latex ${SPHINX_EXECUTABLE}
   -b latex "${DOC_SRC_DIR}" "${DOC_LATEX_DIR}"
@@ -51,8 +46,14 @@ add_custom_target(doc_latex ${SPHINX_EXECUTABLE}
           ${DOC_SRC_DIR}/log.rst ${DOC_SRC_DIR}/connection.rst ${DOC_SRC_DIR}/services.rst
           ${DOC_SRC_DIR}/nodestore.rst ${DOC_SRC_DIR}/information_modelling.rst
           ${DOC_SRC_DIR}/protocol.rst
-          ${DOC_SRC_DIR}/tutorial_server_firststeps.rst
+          ${DOC_SRC_DIR}/tutorial_datatypes.rst
           ${DOC_SRC_DIR}/tutorial_client_firststeps.rst
+          ${DOC_SRC_DIR}/tutorial_server_firststeps.rst
+          ${DOC_SRC_DIR}/tutorial_server_variable.rst
+          ${DOC_SRC_DIR}/tutorial_server_variabletype.rst
+          ${DOC_SRC_DIR}/tutorial_server_datasource.rst
+          ${DOC_SRC_DIR}/tutorial_server_object.rst
+          ${DOC_SRC_DIR}/tutorial_server_method.rst
   COMMENT "Building LaTeX sources for documentation with Sphinx")
 add_dependencies(doc_latex open62541)
 
@@ -71,7 +72,15 @@ add_custom_target(doc ${SPHINX_EXECUTABLE}
           ${DOC_SRC_DIR}/log.rst ${DOC_SRC_DIR}/connection.rst ${DOC_SRC_DIR}/services.rst
           ${DOC_SRC_DIR}/nodestore.rst ${DOC_SRC_DIR}/information_modelling.rst
           ${DOC_SRC_DIR}/protocol.rst
-          ${DOC_SRC_DIR}/tutorial_server_firststeps.rst
+          ${DOC_SRC_DIR}/tutorial_datatypes.rst
           ${DOC_SRC_DIR}/tutorial_client_firststeps.rst
+          ${DOC_SRC_DIR}/tutorial_server_firststeps.rst
+          ${DOC_SRC_DIR}/tutorial_server_variable.rst
+          ${DOC_SRC_DIR}/tutorial_server_variabletype.rst
+          ${DOC_SRC_DIR}/tutorial_server_datasource.rst
+          ${DOC_SRC_DIR}/tutorial_server_object.rst
+          ${DOC_SRC_DIR}/tutorial_server_method.rst
   COMMENT "Building HTML documentation with Sphinx")
 add_dependencies(doc open62541)
+
+set_target_properties(doc doc_latex doc_pdf PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD TRUE)

+ 4 - 7
doc/building.rst

@@ -40,6 +40,10 @@ Building with CMake on Ubuntu or Debian
    ccmake ..
    make
 
+   # build documentation
+   make doc # html documentation
+   make doc_pdf # pdf documentation (requires LaTeX)
+
 Building with CMake on Windows
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
@@ -145,13 +149,6 @@ By default only the shared object libopen62541.so or the library open62541.dll
 and open62541.dll.a resp. open62541.lib are build. Additional artifacts can be
 specified by the following options:
 
-**UA_BUILD_DOCUMENTATION**
-  Generate Make targets for documentation
-
-   * HTML documentation: ``make doc``
-   * Latex Files: ``latex``
-   * PDF documentation: ``make pdf``
-
 **UA_BUILD_EXAMPLES**
    Compile example servers and clients from :file:`examples/{xyz}.c`. A static and a dynamic binary is linked, respectively.
 

File diff suppressed because it is too large
+ 12 - 171
doc/tutorial_noderelations.rst


+ 1 - 0
doc/toc.rst

@@ -13,4 +13,5 @@ open62541 Documentation
    server
    client
    constants
+   namespace_compiler
    internal

+ 0 - 22
doc/tutorial_server_method.rst

@@ -1,22 +0,0 @@
-Adding a server-side method
----------------------------
-
-This tutorial demonstrates how to add method nodes to the server. Use an UA
-client, e.g., UaExpert to call the method (right-click on the method node ->
-call).
-
-The first example shows how to define input and output arguments (lines 72 - 88),
-make the method executable (lines 94,95), add the method node (line 96-101)
-with a specified method callback (lines 10 - 24).
-
-The second example shows that a method can also be applied on an array
-as input argument and output argument.
-
-The last example presents a way to bind a new method callback to an already
-instantiated method node.
-
-
-.. literalinclude:: server_method.c
-   :language: c
-   :linenos:
-   :lines: 4,5,14,16-

+ 0 - 74
doc/tutorial_server_variables.rst

@@ -1,74 +0,0 @@
-.. role:: ccode(code)
-      :language: c
-
-Adding variables to a server
-----------------------------
-
-This tutorial shows how to add variable nodes to a server and how these can be
-connected to a physical process in the background.
-
-This is the code for a server with a single variable node holding an integer. We
-will take this example to explain some of the fundamental concepts of open62541.
-
-.. literalinclude:: server_variable.c
-   :language: c
-   :linenos:
-   :lines: 4,13,15-
-
-Variants and Datatypes
-^^^^^^^^^^^^^^^^^^^^^^
-
-The datatype :ref:`variant` belongs to the built-in datatypes of OPC UA and is
-used as a container type. A variant can hold any other datatype as a scalar
-(except variant) or as an array. Array variants can additionally denote the
-dimensionality of the data (e.g. a 2x3 matrix) in an additional integer array.
-
-The `UA_VariableAttributes` type contains a variant member `value`. The command
-:ccode:`UA_Variant_setScalar(&attr.value, &myInteger,
-&UA_TYPES[UA_TYPES_INT32])` sets the variant to point to the integer. Note that
-this does not make a copy of the integer (for which `UA_Variant_setScalarCopy`
-can be used). The variant (and its content) is then copied into the newly
-created node.
-
-The above code could have used allocations by making copies of all entries of
-the attribute variant. Then, of course, the variant content needs to be deleted
-to prevent memleaks.
-
-.. code-block:: c
-
-    UA_VariableAttributes attr;
-    UA_VariableAttributes_init(&attr);
-    UA_Int32 myInteger = 42;
-    UA_Variant_setScalarCopy(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);
-    attr.description = UA_LOCALIZEDTEXT_ALLOC("en_US","the answer");
-    attr.displayName = UA_LOCALIZEDTEXT_ALLOC("en_US","the answer");
-
-    /* add the variable node here */
-    UA_VariableAttributes_deleteMembers(&attr); /* free the allocated memory */
-
-Finally, one needs to tell the server where to add the new variable in the
-information model. For that, we state the NodeId of the parent node and the
-(hierarchical) reference to the parent node.
-
-NodeIds
-^^^^^^^
-
-A :ref:`nodeid` is a unique identifier in server's context. It contains the
-number of node's namespace and either a numeric, string or GUID (Globally
-Unique ID) identifier. The following are some examples for their usage.
-
-.. code-block:: c
-
-   UA_NodeId id1 = UA_NODEID_NUMERIC(1, 1234);
-
-   UA_NodeId id2 = UA_NODEID_STRING(1, "testid"); /* points to the static string */
-
-   UA_NodeId id3 = UA_NODEID_STRING_ALLOC(1, "testid"); /* copy to memory */
-   UA_NodeId_deleteMembers(&id3); /* free the allocated string */
-
-
-Adding a Variable with an external Data Source
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-In order to couple a running process to a variable, a :ref:`datasource` is used.
-Consider ``examples/server_datasource.c`` in the repository for an example.

+ 5 - 2
doc/tutorials.rst

@@ -3,8 +3,11 @@ Tutorials
 
 .. toctree::
 
+   tutorial_datatypes.rst
    tutorial_server_firststeps.rst
-   tutorial_server_variables.rst
+   tutorial_server_variable.rst
+   tutorial_server_datasource.rst
+   tutorial_server_variabletype.rst
+   tutorial_server_object.rst
    tutorial_server_method.rst
-   tutorial_noderelations.rst
    tutorial_client_firststeps.rst

+ 22 - 12
examples/CMakeLists.txt

@@ -34,32 +34,46 @@ macro(add_example EXAMPLE_NAME EXAMPLE_SOURCE)
   endif()
 endmacro()
 
+#############
+# Tutorials #
+#############
+
+add_example(tutorial_datatypes tutorial_datatypes.c)
+
+add_example(tutorial_server_firststeps tutorial_server_firststeps.c)
+
+add_example(tutorial_server_variable tutorial_server_variable.c)
+
+add_example(tutorial_server_datasource tutorial_server_datasource.c)
+
+add_example(tutorial_server_variabletype tutorial_server_variabletype.c)
+
+add_example(tutorial_server_object tutorial_server_object.c)
+
+if(UA_ENABLE_METHODCALLS)
+  add_example(tutorial_server_method tutorial_server_method.c)
+endif()
+
+add_example(tutorial_client_firststeps tutorial_client_firststeps.c)
+
 ##################
 # Example Server #
 ##################
 
 add_example(server server.c)
 
-add_example(server_firststeps tutorials/server_firststeps.c)
-
 ##################
 # Example Client #
 ##################
 
 add_example(client client.c)
 
-add_example(client_firststeps tutorials/client_firststeps.c)
-
 ####################
 # Feature Examples #
 ####################
 
-add_example(server_variable server_variable.c)
-
 add_example(server_mainloop server_mainloop.c)
 
-add_example(server_datasource server_datasource.c)
-
 add_example(server_instantiation server_instantiation.c)
 
 add_example(server_repeated_job server_repeated_job.c)
@@ -74,10 +88,6 @@ if(UA_BUILD_EXAMPLES_NODESET_COMPILER)
   target_include_directories(server_nodeset PRIVATE ${PROJECT_SOURCE_DIR}/src ${PROJECT_SOURCE_DIR}/deps) # needs an internal header
 endif()
 
-if(UA_ENABLE_METHODCALLS)
-  add_example(server_method server_method.c)
-endif()
-
 if(UA_BUILD_SELFSIGNED_CERTIFICATE)
   find_package(OpenSSL REQUIRED)
   add_custom_command(OUTPUT server_cert.der ca.crt

+ 0 - 74
examples/server_datasource.c

@@ -1,74 +0,0 @@
-/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
- * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
-
-#include <signal.h>
-#include <stdio.h>
-#include "open62541.h"
-
-UA_Boolean running = true;
-UA_Logger logger = UA_Log_Stdout;
-
-static void stopHandler(int sign) {
-    UA_LOG_INFO(logger, UA_LOGCATEGORY_SERVER, "received ctrl-c");
-    running = false;
-}
-
-static UA_StatusCode
-readInteger(void *handle, const UA_NodeId nodeid, UA_Boolean sourceTimeStamp,
-            const UA_NumericRange *range, UA_DataValue *dataValue) {
-    dataValue->hasValue = true;
-    UA_Variant_setScalarCopy(&dataValue->value, (UA_UInt32*)handle, &UA_TYPES[UA_TYPES_INT32]);
-    // we know the nodeid is a string
-    UA_LOG_INFO(logger, UA_LOGCATEGORY_USERLAND, "Node read %.*s",
-                nodeid.identifier.string.length, nodeid.identifier.string.data);
-    UA_LOG_INFO(logger, UA_LOGCATEGORY_USERLAND, "read value %i", *(UA_UInt32*)handle);
-    return UA_STATUSCODE_GOOD;
-}
-
-static UA_StatusCode
-writeInteger(void *handle, const UA_NodeId nodeid,
-             const UA_Variant *data, const UA_NumericRange *range) {
-    if(UA_Variant_isScalar(data) && data->type == &UA_TYPES[UA_TYPES_INT32] && data->data){
-        *(UA_UInt32*)handle = *(UA_UInt32*)data->data;
-    }
-    // we know the nodeid is a string
-    UA_LOG_INFO(logger, UA_LOGCATEGORY_USERLAND, "Node written %.*s",
-                nodeid.identifier.string.length, nodeid.identifier.string.data);
-    UA_LOG_INFO(logger, UA_LOGCATEGORY_USERLAND, "written value %i", *(UA_UInt32*)handle);
-    return UA_STATUSCODE_GOOD;
-}
-
-
-int main(int argc, char** argv) {
-    signal(SIGINT, stopHandler); /* catches ctrl-c */
-
-    UA_ServerConfig config = UA_ServerConfig_standard;
-    UA_ServerNetworkLayer nl = UA_ServerNetworkLayerTCP(UA_ConnectionConfig_standard, 16664);
-    config.networkLayers = &nl;
-    config.networkLayersSize = 1;
-    UA_Server *server = UA_Server_new(config);
-
-    /* add a variable node to the address space */
-    UA_Int32 myInteger = 42;
-    UA_NodeId myIntegerNodeId = UA_NODEID_STRING(1, "the.answer");
-    UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, "the answer");
-    UA_DataSource dataSource;
-    dataSource.handle = &myInteger;
-    dataSource.read = readInteger;
-    dataSource.write = NULL;
-    UA_VariableAttributes attr;
-    UA_VariableAttributes_init(&attr);
-    attr.description = UA_LOCALIZEDTEXT("en_US","the answer");
-    attr.displayName = UA_LOCALIZEDTEXT("en_US","the answer");
-
-    UA_Server_addDataSourceVariableNode(server, myIntegerNodeId,
-                                        UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
-                                        UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
-                                        myIntegerName, UA_NODEID_NULL, attr, dataSource, NULL);
-
-    UA_StatusCode retval = UA_Server_run(server, &running);
-    UA_Server_delete(server);
-    nl.deleteMembers(&nl);
-
-    return (int)retval;
-}

+ 0 - 179
examples/server_method.c

@@ -1,179 +0,0 @@
-/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
- * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
-
-#include <signal.h>
-#include <stdlib.h>
-#include "open62541.h"
-
-/* Example 1 */
-static UA_StatusCode
-helloWorldMethod(void *handle, const UA_NodeId *objectId,
-                 const UA_NodeId *sessionId, void *sessionHandle,
-                 size_t inputSize, const UA_Variant *input,
-                 size_t outputSize, UA_Variant *output) {
-    UA_String *inputStr = (UA_String*)input->data;
-    UA_String tmp = UA_STRING_ALLOC("Hello ");
-    if(inputStr->length > 0) {
-        tmp.data = (UA_Byte*)realloc(tmp.data, tmp.length + inputStr->length);
-        memcpy(&tmp.data[tmp.length], inputStr->data, inputStr->length);
-        tmp.length += inputStr->length;
-    }
-    UA_Variant_setScalarCopy(output, &tmp, &UA_TYPES[UA_TYPES_STRING]);
-    UA_String_deleteMembers(&tmp);
-    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Hello World was called");
-    return UA_STATUSCODE_GOOD;
-}
-
-/* Example 2 */
-static UA_StatusCode
-IncInt32ArrayValuesMethod(void *handle, const UA_NodeId *objectId,
-                          const UA_NodeId *sessionId, void *sessionHandle,
-                          size_t inputSize, const UA_Variant *input,
-                          size_t outputSize, UA_Variant *output) {
-    UA_Variant_setArrayCopy(output, input->data, 5, &UA_TYPES[UA_TYPES_INT32]);
-    for(size_t i = 0; i< input->arrayLength; i++)
-        ((UA_Int32*)output->data)[i] = ((UA_Int32*)input->data)[i] + 1;
-    return UA_STATUSCODE_GOOD;
-}
-
-
-/* Example 3 */
-static UA_StatusCode
-fooBarMethod(void *handle, const UA_NodeId *objectId,
-             const UA_NodeId *sessionId, void *sessionHandle,
-             size_t inputSize, const UA_Variant *input,
-             size_t outputSize, UA_Variant *output) {
-    /* the same as helloWorld, but returns foobar */
-    UA_String *inputStr = (UA_String*)input->data;
-    UA_String tmp = UA_STRING_ALLOC("FooBar! ");
-    if(inputStr->length > 0) {
-        tmp.data = (UA_Byte*)realloc(tmp.data, tmp.length + inputStr->length);
-        memcpy(&tmp.data[tmp.length], inputStr->data, inputStr->length);
-        tmp.length += inputStr->length;
-    }
-    UA_Variant_setScalarCopy(output, &tmp, &UA_TYPES[UA_TYPES_STRING]);
-    UA_String_deleteMembers(&tmp);
-    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "FooBar was called");
-    return UA_STATUSCODE_GOOD;
-}
-
-UA_Boolean running = true;
-static void stopHandler(int sign) {
-    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
-    running = 0;
-}
-
-int main(int argc, char** argv) {
-    signal(SIGINT, stopHandler); /* catches ctrl-c */
-
-    /* initialize the server */
-    UA_ServerConfig config = UA_ServerConfig_standard;
-    UA_ServerNetworkLayer nl;
-    nl = UA_ServerNetworkLayerTCP(UA_ConnectionConfig_standard, 16664);
-    config.networkLayers = &nl;
-    config.networkLayersSize = 1;
-    UA_Server *server = UA_Server_new(config);
-
-    /* Example 1 */
-    /* add the method node with the callback */
-    UA_Argument inputArguments1;
-    UA_Argument_init(&inputArguments1);
-    inputArguments1.arrayDimensionsSize = 0;
-    inputArguments1.arrayDimensions = NULL;
-    inputArguments1.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
-    inputArguments1.description = UA_LOCALIZEDTEXT("en_US", "A String");
-    inputArguments1.name = UA_STRING("MyInput");
-    inputArguments1.valueRank = -1;
-
-    UA_Argument outputArguments1;
-    UA_Argument_init(&outputArguments1);
-    outputArguments1.arrayDimensionsSize = 0;
-    outputArguments1.arrayDimensions = NULL;
-    outputArguments1.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
-    outputArguments1.description = UA_LOCALIZEDTEXT("en_US", "A String");
-    outputArguments1.name = UA_STRING("MyOutput");
-    outputArguments1.valueRank = -1;
-
-    UA_MethodAttributes helloAttr;
-    UA_MethodAttributes_init(&helloAttr);
-    helloAttr.description = UA_LOCALIZEDTEXT("en_US","Say `Hello World`");
-    helloAttr.displayName = UA_LOCALIZEDTEXT("en_US","Hello World");
-    helloAttr.executable = true;
-    helloAttr.userExecutable = true;
-    UA_Server_addMethodNode(server, UA_NODEID_NUMERIC(1,62541),
-                            UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
-                            UA_NODEID_NUMERIC(0, UA_NS0ID_HASORDEREDCOMPONENT),
-                            UA_QUALIFIEDNAME(1, "hello world"),
-                            helloAttr, &helloWorldMethod, NULL,
-                            1, &inputArguments1, 1, &outputArguments1, NULL);
-
-    /* Example 2 */
-    /* add another method node: output argument as 1d Int32 array*/
-    UA_Argument inputArguments2;
-    UA_Argument_init(&inputArguments2);
-    inputArguments2.arrayDimensionsSize = 1;
-    UA_UInt32 * pInputDimensions = UA_UInt32_new();
-    pInputDimensions[0] = 5;
-    inputArguments2.arrayDimensions = pInputDimensions;
-    inputArguments2.dataType = UA_TYPES[UA_TYPES_INT32].typeId;
-    inputArguments2.description = UA_LOCALIZEDTEXT("en_US",
-                    "input an array with 5 elements, type int32");
-    inputArguments2.name = UA_STRING("int32 value");
-    inputArguments2.valueRank = 1;
-
-    UA_Argument outputArguments2;
-    UA_Argument_init(&outputArguments2);
-    outputArguments2.arrayDimensionsSize = 1;
-    UA_UInt32 * pOutputDimensions = UA_UInt32_new();
-    pOutputDimensions[0] = 5;
-    outputArguments2.arrayDimensions = pOutputDimensions;
-    outputArguments2.dataType = UA_TYPES[UA_TYPES_INT32].typeId;
-    outputArguments2.description = UA_LOCALIZEDTEXT("en_US",
-                                                    "increment each array index");
-    outputArguments2.name = UA_STRING("output is the array, "
-                                      "each index is incremented by one");
-    outputArguments2.valueRank = 1;
-
-    UA_MethodAttributes incAttr;
-    UA_MethodAttributes_init(&incAttr);
-    incAttr.description = UA_LOCALIZEDTEXT("en_US", "1dArrayExample");
-    incAttr.displayName = UA_LOCALIZEDTEXT("en_US", "1dArrayExample");
-    incAttr.executable = true;
-    incAttr.userExecutable = true;
-    UA_Server_addMethodNode(server, UA_NODEID_STRING(1, "IncInt32ArrayValues"),
-                            UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
-                            UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
-                            UA_QUALIFIEDNAME(1, "IncInt32ArrayValues"),
-                            incAttr, &IncInt32ArrayValuesMethod, NULL,
-                            1, &inputArguments2, 1, &outputArguments2, NULL);
-
-    /* Example 3 */
-    UA_MethodAttributes method3Attr;
-    UA_MethodAttributes_init(&method3Attr);
-    method3Attr.description = UA_LOCALIZEDTEXT("en_US", "FooBar");
-    method3Attr.displayName = UA_LOCALIZEDTEXT("en_US", "FooBar");
-    method3Attr.executable = true;
-    method3Attr.userExecutable = true;
-    UA_Server_addMethodNode(server, UA_NODEID_STRING(1, "FooBar"),
-                            UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
-                            UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
-                            UA_QUALIFIEDNAME(1, "FooBar"),
-                            method3Attr, NULL, NULL,
-                            1, &inputArguments1, 1, &outputArguments1, NULL);
-    /* If the method node has no callback (because it was instantiated without
-     * one) or if we just want to change it, this can be done
-     * UA_Server_setMethodNode_callback() */
-    UA_Server_setMethodNode_callback(server,  UA_NODEID_NUMERIC(1,62542),
-                                     &fooBarMethod, NULL);
-
-    /* start server */
-    UA_StatusCode retval = UA_Server_run(server, &running);
-
-    /* ctrl-c received -> clean up */
-    UA_UInt32_delete(pInputDimensions);
-    UA_UInt32_delete(pOutputDimensions);
-    UA_Server_delete(server);
-    nl.deleteMembers(&nl);
-
-    return (int)retval;
-}

+ 14 - 13
examples/server_repeated_job.c

@@ -4,23 +4,24 @@
 #include <signal.h>
 #include "open62541.h"
 
-UA_Boolean running = true;
-UA_Logger logger = UA_Log_Stdout;
+static void
+testCallback(UA_Server *server, void *data) {
+    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "testcallback");
+}
 
+UA_Boolean running = true;
 static void stopHandler(int sign) {
-    UA_LOG_INFO(logger, UA_LOGCATEGORY_SERVER, "received ctrl-c");
+    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
     running = false;
 }
 
-static void testCallback(UA_Server *server, void *data) {
-    UA_LOG_INFO(logger, UA_LOGCATEGORY_USERLAND, "testcallback");
-}
-
-int main(int argc, char** argv) {
-    signal(SIGINT, stopHandler); /* catches ctrl-c */
+int main(void) {
+    signal(SIGINT, stopHandler);
+    signal(SIGTERM, stopHandler);
 
     UA_ServerConfig config = UA_ServerConfig_standard;
-    UA_ServerNetworkLayer nl = UA_ServerNetworkLayerTCP(UA_ConnectionConfig_standard, 16664);
+    UA_ServerNetworkLayer nl =
+        UA_ServerNetworkLayerTCP(UA_ConnectionConfig_standard, 16664);
     config.networkLayers = &nl;
     config.networkLayersSize = 1;
     UA_Server *server = UA_Server_new(config);
@@ -30,10 +31,10 @@ int main(int argc, char** argv) {
     job.type = UA_JOBTYPE_METHODCALL;
     job.job.methodCall.data = NULL;
     job.job.methodCall.method = testCallback;
-    UA_Server_addRepeatedJob(server, job, 2000, NULL); // call every 2 sec
+    UA_Server_addRepeatedJob(server, job, 2000, NULL); /* call every 2 sec */
 
-    UA_StatusCode retval = UA_Server_run(server, &running);
+    UA_Server_run(server, &running);
     UA_Server_delete(server);
     nl.deleteMembers(&nl);
-    return (int)retval;
+    return 0;
 }

+ 0 - 84
examples/server_variable.c

@@ -1,84 +0,0 @@
-/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
- * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
-
-#include <signal.h>
-#include "open62541.h"
-
-UA_Boolean running = true;
-UA_Logger logger = UA_Log_Stdout;
-
-static void stopHandler(int sign) {
-    UA_LOG_INFO(logger, UA_LOGCATEGORY_SERVER, "received ctrl-c");
-    running = false;
-}
-
-static void onRead(void *handle, const UA_NodeId nodeid, const UA_Variant *data,
-                   const UA_NumericRange *range) {
-    UA_LOG_INFO(logger, UA_LOGCATEGORY_USERLAND, "onRead; handle is: %i",
-                (uintptr_t)handle);
-}
-
-static void onWrite(void *h, const UA_NodeId nodeid, const UA_Variant *data,
-                    const UA_NumericRange *range) {
-    UA_LOG_INFO(logger, UA_LOGCATEGORY_USERLAND, "onWrite; handle: %i",
-                (uintptr_t)h);
-}
-
-int main(int argc, char** argv) {
-    signal(SIGINT, stopHandler); /* catches ctrl-c */
-
-    UA_ServerConfig config = UA_ServerConfig_standard;
-    UA_ServerNetworkLayer nl;
-    nl = UA_ServerNetworkLayerTCP(UA_ConnectionConfig_standard, 16664);
-    config.networkLayers = &nl;
-    config.networkLayersSize = 1;
-    UA_Server *server = UA_Server_new(config);
-
-    /* 1) Define the attribute of the myInteger variable node */
-    UA_VariableAttributes attr;
-    UA_VariableAttributes_init(&attr);
-    UA_Int32 myInteger = 42;
-    UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);
-    attr.description = UA_LOCALIZEDTEXT("en_US","the answer");
-    attr.displayName = UA_LOCALIZEDTEXT("en_US","the answer");
-
-    /* 2) Add the variable node to the information model */
-    UA_NodeId myIntegerNodeId = UA_NODEID_STRING(1, "the.answer");
-    UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, "the answer");
-    UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
-    UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
-    UA_Server_addVariableNode(server, myIntegerNodeId, parentNodeId,
-                              parentReferenceNodeId, myIntegerName,
-                              UA_NODEID_NULL, attr, NULL, NULL);
-
-    UA_ValueCallback callback = {(void*)7, onRead, onWrite};
-    UA_Server_setVariableNode_valueCallback(server, myIntegerNodeId, callback);
-
-    /* 3) Write another value */
-    myInteger = 43;
-    UA_Variant myVar;
-    UA_Variant_init(&myVar);
-    UA_Variant_setScalar(&myVar, &myInteger, &UA_TYPES[UA_TYPES_INT32]);
-    UA_Server_writeValue(server, myIntegerNodeId, myVar);
-
-    /* 4) Set the status code of the value */
-    UA_WriteValue wv;
-    UA_WriteValue_init(&wv);
-    wv.nodeId = myIntegerNodeId;
-    wv.attributeId = UA_ATTRIBUTEID_VALUE;
-    wv.value.status = UA_STATUSCODE_BADNOTCONNECTED;
-    wv.value.hasStatus = true;
-    UA_Server_write(server, &wv);
-
-    /* 5) Reset to a good statuscode with a value */
-    wv.value.hasStatus = false;
-    wv.value.value = myVar;
-    wv.value.hasValue = true;
-    UA_Server_write(server, &wv);
-
-    UA_StatusCode retval = UA_Server_run(server, &running);
-    UA_Server_delete(server);
-    nl.deleteMembers(&nl);
-
-    return (int)retval;
-}

+ 1 - 1
examples/tutorials/client_firststeps.c

@@ -2,7 +2,7 @@
  * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
 
 /**
- * Building a simple client
+ * Building a Simple Client
  * ------------------------
  * You should already have a basic server from the previous tutorials. open62541
  * provides both a server- and clientside API, so creating a client is as easy as

+ 137 - 0
examples/tutorial_datatypes.c

@@ -0,0 +1,137 @@
+/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
+ * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
+
+/**
+ * .. _types-tutorial:
+ *
+ * Working with Data Types
+ * -----------------------
+ *
+ * OPC UA defines a type system for values that can be encoded in the protocol
+ * messages. This tutorial shows some examples for available data types and
+ * their use. See the section on :ref:`types` for the full definitions.
+ *
+ * Basic Data Handling
+ * ^^^^^^^^^^^^^^^^^^^
+ * This section shows the basic interaction patterns for data types. Make
+ * sure to compare with the type definitions in ``ua_types.h``. */
+
+#include <assert.h>
+#include "open62541.h"
+
+static void
+variables_basic(void) {
+    /* Int32 */
+    UA_Int32 i = 5;
+    UA_Int32 j;
+    UA_Int32_copy(&i, &j);
+
+    UA_Int32 *ip = UA_Int32_new();
+    UA_Int32_copy(&i, ip);
+    UA_Int32_delete(ip);
+
+    /* String */
+    UA_String s;
+    UA_String_init(&s); /* _init zeroes out the entire memory of the datatype */
+    char *test = "test";
+    s.length = strlen(test);
+    s.data = (UA_Byte*)test;
+
+    UA_String s2;
+    UA_String_copy(&s, &s2);
+    UA_String_deleteMembers(&s2); /* Copying heap-allocated the dynamic content */
+
+    UA_String s3 = UA_STRING("test2");
+    UA_String s4 = UA_STRING_ALLOC("test2"); /* Copies the content to the heap */
+    UA_Boolean eq = UA_String_equal(&s3, &s4);
+    UA_String_deleteMembers(&s4);
+    if(!eq)
+        return;
+    
+    /* Structured Type */
+    UA_CallRequest cr;
+    UA_init(&cr, &UA_TYPES[UA_TYPES_CALLREQUEST]); /* Generic method */
+    UA_CallRequest_init(&cr); /* Shorthand for the previous line */
+
+    cr.requestHeader.timestamp = UA_DateTime_now(); /* Members of a structure */
+
+    cr.methodsToCall = UA_Array_new(5, &UA_TYPES[UA_TYPES_CALLMETHODREQUEST]);
+    cr.methodsToCallSize = 5; /* Array size needs to be made known */
+
+    UA_CallRequest *cr2 = UA_CallRequest_new();
+    UA_copy(&cr, cr2, &UA_TYPES[UA_TYPES_CALLREQUEST]);
+    UA_CallRequest_deleteMembers(&cr);
+    UA_CallRequest_delete(cr2);
+}
+
+/**
+ * NodeIds
+ * ^^^^^^^
+ * An OPC UA information model is made up of nodes and references between nodes.
+ * Every node has a unique :ref:`nodeid`. NodeIds refer to a namespace with an
+ * additional identifier value that can be an integer, a string, a guid or a
+ * bytestring. */
+
+static void
+variables_nodeids(void) {
+    UA_NodeId id1 = UA_NODEID_NUMERIC(1, 1234);
+    id1.namespaceIndex = 3;
+
+    UA_NodeId id2 = UA_NODEID_STRING(1, "testid"); /* the string is static */
+    UA_Boolean eq = UA_NodeId_equal(&id1, &id2);
+    if(eq)
+        return;
+
+    UA_NodeId id3;
+    UA_NodeId_copy(&id2, &id3);
+    UA_NodeId_deleteMembers(&id3);
+
+    UA_NodeId id4 = UA_NODEID_STRING_ALLOC(1, "testid"); /* the string is copied
+                                                            to the heap */
+    UA_NodeId_deleteMembers(&id4);
+}
+
+/**
+ * Variants
+ * ^^^^^^^^
+ * The datatype :ref:`variant` belongs to the built-in datatypes of OPC UA and
+ * is used as a container type. A variant can hold any other datatype as a
+ * scalar (except variant) or as an array. Array variants can additionally
+ * denote the dimensionality of the data (e.g. a 2x3 matrix) in an additional
+ * integer array. */
+
+static void
+variables_variants(void) {
+    /* Set a scalar value */
+    UA_Variant v;
+    UA_Int32 i = 42;
+    UA_Variant_setScalar(&v, &i, &UA_TYPES[UA_TYPES_INT32]);
+
+    /* Make a copy */
+    UA_Variant v2;
+    UA_Variant_copy(&v, &v2);
+    UA_Variant_deleteMembers(&v2);
+
+    /* Set an array value */
+    UA_Variant v3;
+    UA_Double d[9] = {1.0, 2.0, 3.0,
+                      4.0, 5.0, 6.0,
+                      7.0, 8.0, 9.0};
+    UA_Variant_setArrayCopy(&v3, d, 9, &UA_TYPES[UA_TYPES_DOUBLE]);
+
+    /* Set array dimensions */
+    v3.arrayDimensions = UA_Array_new(2, &UA_TYPES[UA_TYPES_UINT32]);
+    v3.arrayDimensionsSize = 2;
+    v3.arrayDimensions[0] = 3;
+    v3.arrayDimensions[1] = 3;
+    UA_Variant_deleteMembers(&v3);
+}
+
+/** It follows the main function, making use of the above definitions. */
+
+int main(void) {
+    variables_basic();
+    variables_nodeids();
+    variables_variants();
+    return 0;
+}

+ 190 - 0
examples/tutorial_server_datasource.c

@@ -0,0 +1,190 @@
+/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
+ * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
+
+/**
+ * Connecting a Variable with a Physical Process
+ * ---------------------------------------------
+ *
+ * In OPC UA-based architectures, servers are typically situated near the source
+ * of information. In an industrial context, this translates into servers being
+ * near the physical process and clients consuming the data at runtime. In the
+ * previous tutorial, we saw how to add variables to an OPC UA information
+ * model. This tutorial shows how to connect a variable to runtime information,
+ * for example from measurements of a physical process. For simplicty, we take
+ * the system clock as the underlying "process".
+ *
+ * The following code snippets are each concerned with a different way of
+ * updating variable values at runtime. Taken together, the code snippets define
+ * a compilable source file.
+ *
+ * Updating variables manually
+ * ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ * As a starting point, assume that a variable for a value of type
+ * :ref:`datetime` has been created in the server with the identifier
+ * "ns=1,s=current-time". Assuming that our applications gets triggered when a
+ * new value arrives from the underlying process, we can just write into the
+ * variable. */
+
+#include <signal.h>
+#include "open62541.h"
+
+static void
+updateCurrentTime(UA_Server *server) {
+    UA_DateTime now = UA_DateTime_now();
+    UA_Variant value;
+    UA_Variant_setScalar(&value, &now, &UA_TYPES[UA_TYPES_DATETIME]);
+    UA_NodeId currentNodeId = UA_NODEID_STRING(1, "current-time");
+    UA_Server_writeValue(server, currentNodeId, value);
+}
+
+static void
+addCurrentTimeVariable(UA_Server *server) {
+    UA_DateTime now = 0;
+    UA_VariableAttributes attr;
+    UA_VariableAttributes_init(&attr);
+    attr.displayName = UA_LOCALIZEDTEXT("en_US", "Current time");
+    UA_Variant_setScalar(&attr.value, &now, &UA_TYPES[UA_TYPES_DATETIME]);
+
+    UA_NodeId currentNodeId = UA_NODEID_STRING(1, "current-time");
+    UA_QualifiedName currentName = UA_QUALIFIEDNAME(1, "current-time");
+    UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
+    UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
+    UA_NodeId variableTypeNodeId = UA_NODEID_NULL;
+    UA_Server_addVariableNode(server, currentNodeId, parentNodeId,
+                              parentReferenceNodeId, currentName,
+                              variableTypeNodeId, attr, NULL, NULL);
+
+    updateCurrentTime(server);
+}
+
+/**
+ * Variable Value Callback
+ * ^^^^^^^^^^^^^^^^^^^^^^^
+ *
+ * When a value changes continuously, such as the system time, updating the
+ * value in a tight loop would take up a lot of resources. Value callbacks allow
+ * to synchronize a variable value with an external representation. They attach
+ * callbacks to the variable that are executed before every read and after every
+ * write operation. */
+
+static void
+beforeReadTime(void *handle, const UA_NodeId nodeid, const UA_Variant *data,
+               const UA_NumericRange *range) {
+    UA_Server *server = (UA_Server*)handle;
+    UA_DateTime now = UA_DateTime_now();
+    UA_Variant value;
+    UA_Variant_setScalar(&value, &now, &UA_TYPES[UA_TYPES_DATETIME]);
+    UA_NodeId currentNodeId = UA_NODEID_STRING(1, "current-time");
+    UA_Server_writeValue(server, currentNodeId, value);
+}
+
+static void
+afterWriteTime(void *handle, const UA_NodeId nodeid, const UA_Variant *data,
+               const UA_NumericRange *range) {
+    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+                "The variable was updated");
+}
+
+static void
+addValueCallbackToCurrentTimeVariable(UA_Server *server) {
+    UA_NodeId currentNodeId = UA_NODEID_STRING(1, "current-time");
+    UA_ValueCallback callback ;
+    callback.handle = server;
+    callback.onRead = beforeReadTime;
+    callback.onWrite = afterWriteTime;
+    UA_Server_setVariableNode_valueCallback(server, currentNodeId, callback);
+}
+
+/**
+ * Variable Data Sources
+ * ^^^^^^^^^^^^^^^^^^^^^
+ *
+ * With value callbacks, the value is still stored in the variable node.
+ * So-called data sources go one step further. The server redirects every read
+ * and write request to a callback function. Upon reading, the callback provides
+ * copy of the current value. Internally, the data source needs to implement its
+ * own memory management. */
+
+static UA_StatusCode
+readCurrentTime(void *handle, const UA_NodeId nodeid, UA_Boolean sourceTimeStamp,
+                const UA_NumericRange *range, UA_DataValue *dataValue) {
+    UA_DateTime now = UA_DateTime_now();
+    UA_Variant_setScalarCopy(&dataValue->value, &now,
+                             &UA_TYPES[UA_TYPES_DATETIME]);
+    dataValue->hasValue = true;
+    return UA_STATUSCODE_GOOD;
+}
+
+static UA_StatusCode
+writeCurrentTime(void *handle, const UA_NodeId nodeid, const UA_Variant *data,
+                 const UA_NumericRange *range) {
+    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+                "Changing the system time is not implemented");
+    return UA_STATUSCODE_BADINTERNALERROR;
+}
+
+static void
+addCurrentTimeDataSourceVariable(UA_Server *server) {
+    UA_VariableAttributes attr;
+    UA_VariableAttributes_init(&attr);
+    attr.displayName = UA_LOCALIZEDTEXT("en_US", "Current time - data source");
+
+    UA_NodeId currentNodeId = UA_NODEID_STRING(1, "current-time-datasource");
+    UA_QualifiedName currentName = UA_QUALIFIEDNAME(1, "current-time-datasource");
+    UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
+    UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
+    UA_NodeId variableTypeNodeId = UA_NODEID_NULL;
+
+    UA_DataSource timeDataSource;
+    timeDataSource.handle = NULL;
+    timeDataSource.read = readCurrentTime;
+    timeDataSource.write = writeCurrentTime;
+    UA_Server_addDataSourceVariableNode(server, currentNodeId, parentNodeId,
+                                        parentReferenceNodeId, currentName,
+                                        variableTypeNodeId, attr,
+                                        timeDataSource, NULL);
+}
+
+/** It follows the main server code, making use of the above definitions. */
+
+UA_Boolean running = true;
+static void stopHandler(int sign) {
+    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
+    running = false;
+}
+
+int main(void) {
+    signal(SIGINT, stopHandler);
+    signal(SIGTERM, stopHandler);
+
+    UA_ServerConfig config = UA_ServerConfig_standard;
+    UA_ServerNetworkLayer nl =
+        UA_ServerNetworkLayerTCP(UA_ConnectionConfig_standard, 16664);
+    config.networkLayers = &nl;
+    config.networkLayersSize = 1;
+    UA_Server *server = UA_Server_new(config);
+
+    addCurrentTimeVariable(server);
+    addValueCallbackToCurrentTimeVariable(server);
+    addCurrentTimeDataSourceVariable(server);
+
+    UA_Server_run(server, &running);
+    UA_Server_delete(server);
+    nl.deleteMembers(&nl);
+    return 0;
+}
+
+/**
+ * DataChange Notifications
+ * ^^^^^^^^^^^^^^^^^^^^^^^^
+ * A client that is interested in the current value of a variable does not need
+ * to regularly poll the variable. Instead, he can use the Subscription
+ * mechanism to be notified about changes.
+ *
+ * Within a Subscription, the client adds so-called MonitoredItems. A DataChange
+ * MonitoredItem defines a node attribute (usually the value attribute) that is
+ * monitored for changes. The server internally reads the value in the defined
+ * interval and generates the appropriate notifications. The three ways of
+ * updating node values discussed above are all usable in combination with
+ * notifications. That is because notifications use the standard *Read* service
+ * to look for value changes. */

+ 111 - 0
examples/tutorial_server_firststeps.c

@@ -0,0 +1,111 @@
+/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
+ * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
+
+/**
+ * Building a Simple Server
+ * ------------------------
+ *
+ * This series of tutorial guide you through your first steps with open62541.
+ * For compiling the examples, you need a compiler (MS Visual Studio 2015 or
+ * newer, GCC, Clang and MinGW32 are all known to be working). The compilation
+ * instructions are given for GCC but should be straightforward to adapt.
+ *
+ * It will also be very helpful to install an OPC UA Client with a graphical
+ * frontend, such as UAExpert by Unified Automation. That will enable you to
+ * examine the information model of any OPC UA server.
+ *
+ * To get started, downdload the open62541 single-file release from
+ * http://open62541.org or generate it according to the :ref:`build instructions
+ * <building>` with the "amalgamation" option enabled. From now on, we assume
+ * you have the ``open62541.c/.h`` files in the current folder. Now create a new
+ * C source-file called ``myServer.c`` with the following content: */
+
+#include <signal.h>
+#include "open62541.h"
+
+UA_Boolean running = true;
+static void stopHandler(int sig) {
+    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "received ctrl-c");
+    running = false;
+}
+
+int main(void) {
+    signal(SIGINT, stopHandler);
+    signal(SIGTERM, stopHandler);
+
+    UA_ServerConfig config = UA_ServerConfig_standard;
+    UA_ServerNetworkLayer nl =
+        UA_ServerNetworkLayerTCP(UA_ConnectionConfig_standard, 4840);
+    config.networkLayers = &nl;
+    config.networkLayersSize = 1;
+    UA_Server *server = UA_Server_new(config);
+
+    UA_Server_run(server, &running);
+
+    UA_Server_delete(server);
+    nl.deleteMembers(&nl);
+    return 0;
+}
+
+/**
+ * This is all that is needed for a simple OPC UA server. With the GCC compiler,
+ * the following command produces an executable:
+ *
+ * .. code-block:: bash
+ *
+ *    $ gcc -std=c99 open62541.c myServer.c -o myServer
+ *
+ * Now start the server (stop with ctrl-c):
+ *
+ * .. code-block:: bash
+ *
+ *    $ ./myServer
+ *
+ * You have now compiled and run your first OPC UA server. You can go ahead and
+ * browse the information model with client. The server is listening on
+ * ``opc.tcp://localhost:4840``. In the next two sections, we will continue to
+ * explain the different parts of the code in detail.
+ *
+ * Server Configuration and Plugins
+ * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ *
+ * *open62541* provides a flexible framework for building OPC UA servers and
+ * clients. The goals is to have a core library that accomodates for all use
+ * cases and runs on all platforms. Users can then adjust the library to fit
+ * their use case via configuration and by developing (platform-specific)
+ * plugins. The core library is based on C99 only and does not even require
+ * basic POSIX support. For example, the lowlevel networking code is implemented
+ * as an exchangeable plugin. But don't worry. *open62541* provides plugin
+ * implementations for most platforms and sensible default configurations
+ * out-of-the-box.
+ * 
+ * In the above server code, we simply take the default server configuration and
+ * add a single TCP network layer that is listerning on port 4840.
+ *
+ * Server Lifecycle
+ * ^^^^^^^^^^^^^^^^
+ * The code in this example shows the three parts for server lifecycle
+ * management: Creating a server, running the server, and deleting the server.
+ * Creating and deleting a server is trivial once the configuration is set up.
+ * The server is started with ``UA_Server_run``. Internally, the server then
+ * uses timeouts to schedule regular tasks. Between the timeouts, the server
+ * listens on the network layer for incoming messages.
+ *
+ * You might ask how the server knows when to stop running. For this, we have
+ * created a global variable ``running``. Furthermore, we have registered the
+ * method ``stopHandler`` that catches the signal (interrupt) the program
+ * receives when the operating systems tries to close it. This happens for
+ * example when you press ctrl-c in a terminal program. The signal handler then
+ * sets the variable ``running`` to false and the server shuts down once it
+ * takes back control. [#f1]_
+ *
+ * In order to integrated OPC UA in a single-threaded application with its own
+ * mainloop (for example provided by a GUI toolkit), one can alternatively drive
+ * the server manually. See the section of the server documentation on
+ * :ref:`server-lifecycle` for details.
+ *
+ * The server configuration and lifecycle management is needed for all servers.
+ * We will use it in the following tutorials without further comment.
+ *
+ * .. [#f1] Be careful with global variables in multi-threaded applications. You
+ *          might want to allocate the ``running`` variable on the heap. */

+ 183 - 0
examples/tutorial_server_method.c

@@ -0,0 +1,183 @@
+/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
+ * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
+
+/**
+ * Adding Methods to Objects
+ * -------------------------
+ *
+ * An object in an OPC UA information model may contain methods similar to
+ * objects in a programming language. Methods are represented by a MethodNode.
+ * Note that several objects may reference the same MethodNode. When an object
+ * type is instantiated, a reference to the method is added instead of copying
+ * the MethodNode. Therefore, the identifier of the context object is always
+ * explicitly stated when a method is called.
+ *
+ * The method callback takes as input a custom data pointer attached to the
+ * method node, the identifier of the object from which the method is called,
+ * and two arrays for the input and output arguments. The input and output
+ * arguments are all of type :ref:`variant`. Each variant may in turn contain a
+ * (multi-dimensional) array or scalar of any data type.
+ *
+ * Constraints for the method arguments are defined in terms of data type, value
+ * rank and array dimension (similar to variable definitions). The argument
+ * definitions are stored in child VariableNodes of the MethodNode with the
+ * respective BrowseNames ``(0, "InputArguments")`` and ``(0,
+ * "OutputArguments")``.
+ *
+ * Example: Hello World Method
+ * ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ * The method takes a string scalar and returns a string scalar with "Hello "
+ * prepended. The type and length of the input arguments is checked internally
+ * by the SDK, so that we don't have to verify the arguments in the callback. */
+
+#include <signal.h>
+#include "open62541.h"
+
+static UA_StatusCode
+helloWorldMethodCallback(void *handle, const UA_NodeId *objectId,
+                         const UA_NodeId *sessionId, void *sessionHandle,
+                         size_t inputSize, const UA_Variant *input,
+                         size_t outputSize, UA_Variant *output) {
+    UA_String *inputStr = (UA_String*)input->data;
+    UA_String tmp = UA_STRING_ALLOC("Hello ");
+    if(inputStr->length > 0) {
+        tmp.data = realloc(tmp.data, tmp.length + inputStr->length);
+        memcpy(&tmp.data[tmp.length], inputStr->data, inputStr->length);
+        tmp.length += inputStr->length;
+    }
+    UA_Variant_setScalarCopy(output, &tmp, &UA_TYPES[UA_TYPES_STRING]);
+    UA_String_deleteMembers(&tmp);
+    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Hello World was called");
+    return UA_STATUSCODE_GOOD;
+}
+
+static void
+addHellWorldMethod(UA_Server *server) {
+    UA_Argument inputArgument;
+    UA_Argument_init(&inputArgument);
+    inputArgument.description = UA_LOCALIZEDTEXT("en_US", "A String");
+    inputArgument.name = UA_STRING("MyInput");
+    inputArgument.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
+    inputArgument.valueRank = -1; /* scalar */
+
+    UA_Argument outputArgument;
+    UA_Argument_init(&outputArgument);
+    outputArgument.description = UA_LOCALIZEDTEXT("en_US", "A String");
+    outputArgument.name = UA_STRING("MyOutput");
+    outputArgument.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
+    outputArgument.valueRank = -1; /* scalar */
+
+    UA_MethodAttributes helloAttr;
+    UA_MethodAttributes_init(&helloAttr);
+    helloAttr.description = UA_LOCALIZEDTEXT("en_US","Say `Hello World`");
+    helloAttr.displayName = UA_LOCALIZEDTEXT("en_US","Hello World");
+    helloAttr.executable = true;
+    helloAttr.userExecutable = true;
+    UA_Server_addMethodNode(server, UA_NODEID_NUMERIC(1,62541),
+                            UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
+                            UA_NODEID_NUMERIC(0, UA_NS0ID_HASORDEREDCOMPONENT),
+                            UA_QUALIFIEDNAME(1, "hello world"),
+                            helloAttr, &helloWorldMethodCallback, NULL,
+                            1, &inputArgument, 1, &outputArgument, NULL);
+}
+
+/**
+ * Increase Array Values Method
+ * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ * The method takes an array of 5 integers and a scalar as input. It returns a
+ * copy of the array with every entry increased by the scalar. */
+
+static UA_StatusCode
+IncInt32ArrayMethodCallback(void *handle, const UA_NodeId *objectId,
+                            const UA_NodeId *sessionId, void *sessionHandle,
+                            size_t inputSize, const UA_Variant *input,
+                            size_t outputSize, UA_Variant *output) {
+    UA_Int32 *inputArray = (UA_Int32*)input[0].data;
+    UA_Int32 delta = *(UA_Int32*)input[1].data;
+
+    /* Copy the input array */
+    UA_StatusCode retval = UA_Variant_setArrayCopy(output, inputArray, 5,
+                                                   &UA_TYPES[UA_TYPES_INT32]);
+    if(retval != UA_STATUSCODE_GOOD)
+        return retval;
+
+    /* Increate the elements */
+    UA_Int32 *outputArray = (UA_Int32*)output->data;
+    for(size_t i = 0; i < input->arrayLength; i++)
+        outputArray[i] = inputArray[i] + delta;
+
+    return UA_STATUSCODE_GOOD;
+}
+
+static void
+addIncInt32ArrayMethod(UA_Server *server) {
+    /* Two input arguments */
+    UA_Argument inputArguments[2];
+    UA_Argument_init(&inputArguments[0]);
+    inputArguments[0].description = UA_LOCALIZEDTEXT("en_US", "int32[5] array");
+    inputArguments[0].name = UA_STRING("int32 array");
+    inputArguments[0].dataType = UA_TYPES[UA_TYPES_INT32].typeId;
+    inputArguments[0].valueRank = 1;
+    UA_UInt32 pInputDimension = 5;
+    inputArguments[0].arrayDimensionsSize = 1;
+    inputArguments[0].arrayDimensions = &pInputDimension;
+
+    UA_Argument_init(&inputArguments[1]);
+    inputArguments[1].description = UA_LOCALIZEDTEXT("en_US", "int32 delta");
+    inputArguments[1].name = UA_STRING("int32 delta");
+    inputArguments[1].dataType = UA_TYPES[UA_TYPES_INT32].typeId;
+    inputArguments[1].valueRank = -1; /* scalar */
+
+    /* One output argument */
+    UA_Argument outputArgument;
+    UA_Argument_init(&outputArgument);
+    outputArgument.description = UA_LOCALIZEDTEXT("en_US", "int32[5] array");
+    outputArgument.name = UA_STRING("each entry is incremented by the delta");
+    outputArgument.dataType = UA_TYPES[UA_TYPES_INT32].typeId;
+    outputArgument.valueRank = 1;
+    UA_UInt32 pOutputDimension = 5;
+    outputArgument.arrayDimensionsSize = 1;
+    outputArgument.arrayDimensions = &pOutputDimension;
+
+    /* Add the method node */
+    UA_MethodAttributes incAttr;
+    UA_MethodAttributes_init(&incAttr);
+    incAttr.description = UA_LOCALIZEDTEXT("en_US", "IncInt32ArrayValues");
+    incAttr.displayName = UA_LOCALIZEDTEXT("en_US", "IncInt32ArrayValues");
+    incAttr.executable = true;
+    incAttr.userExecutable = true;
+    UA_Server_addMethodNode(server, UA_NODEID_STRING(1, "IncInt32ArrayValues"),
+                            UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
+                            UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
+                            UA_QUALIFIEDNAME(1, "IncInt32ArrayValues"),
+                            incAttr, &IncInt32ArrayMethodCallback, NULL,
+                            2, inputArguments, 1, &outputArgument, NULL);
+}
+
+/** It follows the main server code, making use of the above definitions. */
+
+UA_Boolean running = true;
+static void stopHandler(int sign) {
+    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
+    running = false;
+}
+
+int main(void) {
+    signal(SIGINT, stopHandler);
+    signal(SIGTERM, stopHandler);
+
+    UA_ServerConfig config = UA_ServerConfig_standard;
+    UA_ServerNetworkLayer nl =
+        UA_ServerNetworkLayerTCP(UA_ConnectionConfig_standard, 16664);
+    config.networkLayers = &nl;
+    config.networkLayersSize = 1;
+    UA_Server *server = UA_Server_new(config);
+
+    addHellWorldMethod(server);
+    addIncInt32ArrayMethod(server);
+
+    UA_Server_run(server, &running);
+    UA_Server_delete(server);
+    nl.deleteMembers(&nl);
+    return 0;
+}

+ 336 - 0
examples/tutorial_server_object.c

@@ -0,0 +1,336 @@
+/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
+ * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
+
+/**
+ * Working with Objects and Object Types
+ * -------------------------------------
+ *
+ * Using objects to structure information models
+ * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ * Assume a situation where we want to model a set of pumps and their runtime
+ * state in an OPC UA information model. Of course, all pump representations
+ * should follow the same basic structure, For example, we might have graphical
+ * representation of pumps in a SCADA visualisation that shall be resuable for
+ * all pumps.
+ *
+ * Following the object-oriented programming paradigm, every pump is represented
+ * by an object with the following layout:
+ *
+ * .. graphviz::
+ *
+ *    digraph tree {
+ *
+ *    fixedsize=true;
+ *    node [width=2, height=0, shape=box, fillcolor="#E5E5E5", concentrate=true]
+ *
+ *    node_root [label=< <I>ObjectNode</I><BR/>Pump >]
+ *
+ *    { rank=same
+ *      point_1 [shape=point]
+ *      node_1 [label=< <I>VariableNode</I><BR/>ManufacturerName >] }
+ *    node_root -> point_1 [arrowhead=none]
+ *    point_1 -> node_1 [label="hasComponent"]
+ *
+ *    { rank=same
+ *      point_2 [shape=point]
+ *      node_2 [label=< <I>VariableNode</I><BR/>ModelName >] }
+ *    point_1 -> point_2 [arrowhead=none]
+ *    point_2 -> node_2 [label="hasComponent"]
+ *
+ *    {  rank=same
+ *       point_4 [shape=point]
+ *       node_4 [label=< <I>VariableNode</I><BR/>Status >] }
+ *    point_2 -> point_4 [arrowhead=none]
+ *    point_4 -> node_4 [label="hasComponent"]
+ *
+ *    {  rank=same
+ *       point_5 [shape=point]
+ *       node_5 [label=< <I>VariableNode</I><BR/>MotorRPM >] }
+ *    point_4 -> point_5 [arrowhead=none]
+ *    point_5 -> node_5 [label="hasComponent"]
+ *
+ *    }
+ *
+ * The following code manually defines a pump and its member variables. We omit
+ * setting constraints on the variable values as this is not the focus of this
+ * tutorial and was already covered. */
+
+#include <signal.h>
+#include "open62541.h"
+
+static void
+manuallyDefinePump(UA_Server *server) {
+    UA_NodeId pumpId; /* get the nodeid assigned by the server */
+    UA_ObjectAttributes oAttr;
+    UA_ObjectAttributes_init(&oAttr);
+    oAttr.displayName = UA_LOCALIZEDTEXT("en_US", "Pump (Manual)");
+    UA_Server_addObjectNode(server, UA_NODEID_NULL,
+                            UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
+                            UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
+                            UA_QUALIFIEDNAME(1, "Pump (Manual)"), UA_NODEID_NULL,
+                            oAttr, NULL, &pumpId);
+
+    UA_VariableAttributes mnAttr;
+    UA_VariableAttributes_init(&mnAttr);
+    UA_String manufacturerName = UA_STRING("Pump King Ltd.");
+    UA_Variant_setScalar(&mnAttr.value, &manufacturerName, &UA_TYPES[UA_TYPES_STRING]);
+    mnAttr.displayName = UA_LOCALIZEDTEXT("en_US", "ManufacturerName");
+    UA_Server_addVariableNode(server, UA_NODEID_NULL, pumpId,
+                              UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
+                              UA_QUALIFIEDNAME(1, "ManufacturerName"),
+                              UA_NODEID_NULL, mnAttr, NULL, NULL);
+
+    UA_VariableAttributes modelAttr;
+    UA_VariableAttributes_init(&modelAttr);
+    UA_String modelName = UA_STRING("Mega Pump 3000");
+    UA_Variant_setScalar(&modelAttr.value, &modelName, &UA_TYPES[UA_TYPES_STRING]);
+    modelAttr.displayName = UA_LOCALIZEDTEXT("en_US", "ModelName");
+    UA_Server_addVariableNode(server, UA_NODEID_NULL, pumpId,
+                              UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
+                              UA_QUALIFIEDNAME(1, "ModelName"),
+                              UA_NODEID_NULL, modelAttr, NULL, NULL);
+
+    UA_VariableAttributes statusAttr;
+    UA_VariableAttributes_init(&statusAttr);
+    UA_Boolean status = true;
+    UA_Variant_setScalar(&statusAttr.value, &status, &UA_TYPES[UA_TYPES_BOOLEAN]);
+    statusAttr.displayName = UA_LOCALIZEDTEXT("en_US", "Status");
+    UA_Server_addVariableNode(server, UA_NODEID_NULL, pumpId,
+                              UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
+                              UA_QUALIFIEDNAME(1, "Status"),
+                              UA_NODEID_NULL, statusAttr, NULL, NULL);
+
+    UA_VariableAttributes rpmAttr;
+    UA_VariableAttributes_init(&rpmAttr);
+    UA_Double rpm = 50.0;
+    UA_Variant_setScalar(&rpmAttr.value, &rpm, &UA_TYPES[UA_TYPES_DOUBLE]);
+    rpmAttr.displayName = UA_LOCALIZEDTEXT("en_US", "MotorRPM");
+    UA_Server_addVariableNode(server, UA_NODEID_NULL, pumpId,
+                              UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
+                              UA_QUALIFIEDNAME(1, "MotorRPMs"),
+                              UA_NODEID_NULL, rpmAttr, NULL, NULL);
+}
+
+/**
+ * Object types, type hierarchies and instantiation
+ * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ * Building up each object manually requires us to write a lot of code.
+ * Furthermore, there is no way for clients to detect that an object represents
+ * a pump. (We might use naming conventions or similar to detect pumps. But
+ * that's not exactly a clean solution.) Furthermore, we might have more devices
+ * than just pumps. And we require all devices to share some common structure.
+ * The solution is to define ObjectTypes in a hierarchy with inheritance
+ * relations.
+ *
+ * .. graphviz::
+ *
+ *    digraph tree {
+ *
+ *    fixedsize=true;
+ *    node [width=2, height=0, shape=box, fillcolor="#E5E5E5", concentrate=true]
+ *
+ *    node_root [label=< <I>ObjectTypeNode</I><BR/>Device >]
+ *
+ *    { rank=same
+ *      point_1 [shape=point]
+ *      node_1 [label=< <I>VariableNode</I><BR/>ManufacturerName >] }
+ *    node_root -> point_1 [arrowhead=none]
+ *    point_1 -> node_1 [label="hasComponent"]
+ *
+ *    { rank=same
+ *      point_2 [shape=point]
+ *      node_2 [label=< <I>VariableNode</I><BR/>ModelName >] }
+ *    point_1 -> point_2 [arrowhead=none]
+ *    point_2 -> node_2 [label="hasComponent"]
+ *
+ *    {  rank=same
+ *       point_3 [shape=point]
+ *       node_3 [label=< <I>ObjectTypeNode</I><BR/>Pump >] }
+ *    point_2 -> point_3 [arrowhead=none]
+ *    point_3 -> node_3 [label="hasSubtype"]
+ *
+ *    {  rank=same
+ *       point_4 [shape=point]
+ *       node_4 [label=< <I>VariableNode</I><BR/>Status >] }
+ *    node_3 -> point_4 [arrowhead=none]
+ *    point_4 -> node_4 [label="hasComponent"]
+ *
+ *    {  rank=same
+ *       point_5 [shape=point]
+ *       node_5 [label=< <I>VariableNode</I><BR/>MotorRPM >] }
+ *    point_4 -> point_5 [arrowhead=none]
+ *    point_5 -> node_5 [label="hasComponent"]
+ *
+ *    }
+ *
+ */
+
+/* predefined identifier for later use */
+UA_NodeId pumpTypeId = {1, UA_NODEIDTYPE_NUMERIC, {1001}};
+
+static void
+defineObjectTypes(UA_Server *server) {
+    /* Define the object type for "Device" */
+    UA_NodeId deviceTypeId; /* get the nodeid assigned by the server */
+    UA_ObjectTypeAttributes dtAttr;
+    UA_ObjectTypeAttributes_init(&dtAttr);
+    dtAttr.displayName = UA_LOCALIZEDTEXT("en_US", "DeviceType");
+    UA_Server_addObjectTypeNode(server, UA_NODEID_NULL,
+                                UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
+                                UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
+                                UA_QUALIFIEDNAME(1, "DeviceType"), dtAttr,
+                                NULL, &deviceTypeId);
+
+    UA_VariableAttributes mnAttr;
+    UA_VariableAttributes_init(&mnAttr);
+    mnAttr.displayName = UA_LOCALIZEDTEXT("en_US", "ManufacturerName");
+    UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
+                              UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
+                              UA_QUALIFIEDNAME(1, "ManufacturerName"),
+                              UA_NODEID_NULL, mnAttr, NULL, NULL);
+
+    UA_VariableAttributes modelAttr;
+    UA_VariableAttributes_init(&modelAttr);
+    modelAttr.displayName = UA_LOCALIZEDTEXT("en_US", "ModelName");
+    UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
+                              UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
+                              UA_QUALIFIEDNAME(1, "ModelName"),
+                              UA_NODEID_NULL, modelAttr, NULL, NULL);
+
+    /* Define the object type for "Pump" */
+    UA_ObjectTypeAttributes ptAttr;
+    UA_ObjectTypeAttributes_init(&ptAttr);
+    ptAttr.displayName = UA_LOCALIZEDTEXT("en_US", "PumpType");
+    UA_Server_addObjectTypeNode(server, pumpTypeId,
+                                deviceTypeId, UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
+                                UA_QUALIFIEDNAME(1, "PumpType"), ptAttr,
+                                NULL, NULL);
+
+    UA_VariableAttributes statusAttr;
+    UA_VariableAttributes_init(&statusAttr);
+    statusAttr.displayName = UA_LOCALIZEDTEXT("en_US", "Status");
+    statusAttr.valueRank = -1;
+    UA_Server_addVariableNode(server, UA_NODEID_NULL, pumpTypeId,
+                              UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
+                              UA_QUALIFIEDNAME(1, "Status"),
+                              UA_NODEID_NULL, statusAttr, NULL, NULL);
+
+    UA_VariableAttributes rpmAttr;
+    UA_VariableAttributes_init(&rpmAttr);
+    rpmAttr.displayName = UA_LOCALIZEDTEXT("en_US", "MotorRPM");
+    rpmAttr.valueRank = -1;
+    UA_Server_addVariableNode(server, UA_NODEID_NULL, pumpTypeId,
+                              UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
+                              UA_QUALIFIEDNAME(1, "MotorRPMs"),
+                              UA_NODEID_NULL, rpmAttr, NULL, NULL);
+}
+
+/**
+ * Now we add the derived ObjectType for the pump that inherits from the device
+ * object type. The resulting object contains all four inherited child
+ * variables. The object has a reference of type ``hasTypeDefinition`` to the
+ * object type. Clients can browse this information at runtime and adjust
+ * accordingly.
+ */
+
+static void
+addPumpObjectInstance(UA_Server *server, char *name) {
+    UA_ObjectAttributes oAttr;
+    UA_ObjectAttributes_init(&oAttr);
+    oAttr.displayName = UA_LOCALIZEDTEXT("en_US", name);
+    UA_Server_addObjectNode(server, UA_NODEID_NULL,
+                            UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
+                            UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
+                            UA_QUALIFIEDNAME(1, name),
+                            pumpTypeId, /* this refers to the object type
+                                           identifier */
+                            oAttr, NULL, NULL);
+}
+
+/**
+ * Often times, we want to run a constructor function on a new object. This is
+ * especially useful when an object is instantiated at runtime (with the
+ * AddNodes service) and the integration with an underlying process canot be
+ * manually defined. In the following constructor example, we simply set the
+ * pump status to on.
+ */
+
+UA_Server *s = NULL; /* required to get the server pointer into the constructor
+                        function (will change for v0.3) */
+
+static void *
+pumpTypeConstructor(const UA_NodeId instance) {
+    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "New pump created");
+
+    /* Find the NodeId of the status child variable */
+    UA_RelativePathElement rpe;
+    UA_RelativePathElement_init(&rpe);
+    rpe.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT);
+    rpe.isInverse = false;
+    rpe.includeSubtypes = false;
+    rpe.targetName = UA_QUALIFIEDNAME(1, "Status");
+    
+    UA_BrowsePath bp;
+    UA_BrowsePath_init(&bp);
+    bp.startingNode = instance;
+    bp.relativePath.elementsSize = 1;
+    bp.relativePath.elements = &rpe;
+    
+    UA_BrowsePathResult bpr =
+        UA_Server_translateBrowsePathToNodeIds(s, &bp);
+    if(bpr.statusCode != UA_STATUSCODE_GOOD ||
+       bpr.targetsSize < 1)
+        return NULL;
+
+    /* Set the status value */
+    UA_Boolean status = true;
+    UA_Variant value;
+    UA_Variant_setScalar(&value, &status, &UA_TYPES[UA_TYPES_BOOLEAN]);
+    UA_Server_writeValue(s, bpr.targets[0].targetId.nodeId, value);
+    UA_BrowsePathResult_deleteMembers(&bpr);
+
+    /* The return pointer of the constructor is attached to the ObjectNode */
+    return NULL;
+}
+
+static void
+addPumpTypeConstructor(UA_Server *server) {
+    UA_ObjectLifecycleManagement olm;
+    olm.constructor = pumpTypeConstructor;
+    olm.destructor = NULL;
+    UA_Server_setObjectTypeNode_lifecycleManagement(server, pumpTypeId, olm);
+}
+
+/** It follows the main server code, making use of the above definitions. */
+
+UA_Boolean running = true;
+static void stopHandler(int sign) {
+    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
+    running = false;
+}
+
+int main(void) {
+    signal(SIGINT, stopHandler);
+    signal(SIGTERM, stopHandler);
+
+    UA_ServerConfig config = UA_ServerConfig_standard;
+    UA_ServerNetworkLayer nl =
+        UA_ServerNetworkLayerTCP(UA_ConnectionConfig_standard, 16664);
+    config.networkLayers = &nl;
+    config.networkLayersSize = 1;
+    UA_Server *server = UA_Server_new(config);
+    s = server; /* required for the constructor */
+
+    manuallyDefinePump(server);
+    defineObjectTypes(server);
+    addPumpObjectInstance(server, "pump2");
+    addPumpObjectInstance(server, "pump3");
+    addPumpTypeConstructor(server);
+    addPumpObjectInstance(server, "pump4");
+    addPumpObjectInstance(server, "pump5");
+
+    UA_Server_run(server, &running);
+    UA_Server_delete(server);
+    nl.deleteMembers(&nl);
+    return 0;
+}

+ 121 - 0
examples/tutorial_server_variable.c

@@ -0,0 +1,121 @@
+/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
+ * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
+
+/**
+ * Adding Variables to a Server
+ * ----------------------------
+ *
+ * This tutorial shows how to work with data types and how to add variable nodes
+ * to a server. First, we add a new variable to the server. Take a look at the
+ * definition of the ``UA_VariableAttrbitues`` structure to see the list of all
+ * attributes defined for VariableNodes.
+ */
+
+#include <signal.h>
+#include <stdio.h>
+#include "open62541.h"
+
+static void
+addVariable(UA_Server *server) {
+    /* Define the attribute of the myInteger variable node */
+    UA_VariableAttributes attr;
+    UA_VariableAttributes_init(&attr);
+    UA_Int32 myInteger = 42;
+    UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);
+    attr.description = UA_LOCALIZEDTEXT("en_US","the answer");
+    attr.displayName = UA_LOCALIZEDTEXT("en_US","the answer");
+    attr.dataType = UA_TYPES[UA_TYPES_INT32].typeId;
+
+    /* Add the variable node to the information model */
+    UA_NodeId myIntegerNodeId = UA_NODEID_STRING(1, "the.answer");
+    UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, "the answer");
+    UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
+    UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
+    UA_Server_addVariableNode(server, myIntegerNodeId, parentNodeId,
+                              parentReferenceNodeId, myIntegerName,
+                              UA_NODEID_NULL, attr, NULL, NULL);
+}
+
+/**
+ * Now we change the value with the write service. This uses the same service
+ * implementation that can also be reached over the network by an OPC UA client.
+ */
+
+static void
+writeVariable(UA_Server *server) {
+    UA_NodeId myIntegerNodeId = UA_NODEID_STRING(1, "the.answer");
+
+    /* Write a different integer value */
+    UA_Int32 myInteger = 43;
+    UA_Variant myVar;
+    UA_Variant_init(&myVar);
+    UA_Variant_setScalar(&myVar, &myInteger, &UA_TYPES[UA_TYPES_INT32]);
+    UA_Server_writeValue(server, myIntegerNodeId, myVar);
+
+    /* Set the status code of the value to an error code. The function
+     * UA_Server_write provides access to the raw service. The above
+     * UA_Server_writeValue is syntactic sugar for writing a specific node
+     * attribute with the write service. */
+    UA_WriteValue wv;
+    UA_WriteValue_init(&wv);
+    wv.nodeId = myIntegerNodeId;
+    wv.attributeId = UA_ATTRIBUTEID_VALUE;
+    wv.value.status = UA_STATUSCODE_BADNOTCONNECTED;
+    wv.value.hasStatus = true;
+    UA_Server_write(server, &wv);
+
+    /* Reset the variable to a good statuscode with a value */
+    wv.value.hasStatus = false;
+    wv.value.value = myVar;
+    wv.value.hasValue = true;
+    UA_Server_write(server, &wv);
+}
+
+/**
+ * Note how we initially set the DataType attribute of the variable node to the
+ * NodeId of the Int32 data type. This forbids writing values that are not an
+ * Int32. The following code shows how this consistency check is performed for
+ * every write.
+ */
+
+static void
+writeWrongVariable(UA_Server *server) {
+    UA_NodeId myIntegerNodeId = UA_NODEID_STRING(1, "the.answer");
+
+    /* Write a string */
+    UA_String myString = UA_STRING("test");
+    UA_Variant myVar;
+    UA_Variant_init(&myVar);
+    UA_Variant_setScalar(&myVar, &myString, &UA_TYPES[UA_TYPES_STRING]);
+    UA_StatusCode retval = UA_Server_writeValue(server, myIntegerNodeId, myVar);
+    printf("Writing a string returned statuscode %s\n", UA_StatusCode_name(retval));
+}
+
+/** It follows the main server code, making use of the above definitions. */
+
+UA_Boolean running = true;
+static void stopHandler(int sign) {
+    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
+    running = false;
+}
+
+int main(void) {
+    signal(SIGINT, stopHandler);
+    signal(SIGTERM, stopHandler);
+
+    UA_ServerConfig config = UA_ServerConfig_standard;
+    UA_ServerNetworkLayer nl =
+        UA_ServerNetworkLayerTCP(UA_ConnectionConfig_standard, 16664);
+    config.networkLayers = &nl;
+    config.networkLayersSize = 1;
+    UA_Server *server = UA_Server_new(config);
+
+    addVariable(server);
+    writeVariable(server);
+    writeWrongVariable(server);
+
+    UA_Server_run(server, &running);
+    UA_Server_delete(server);
+    nl.deleteMembers(&nl);
+    return 0;
+}

+ 143 - 0
examples/tutorial_server_variabletype.c

@@ -0,0 +1,143 @@
+/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
+ * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
+
+/**
+ * Working with Variable Types
+ * ---------------------------
+ *
+ * Variable types have three functions:
+ *
+ * - Constrain the possible data type, value rank and array dimensions of the
+ *   variables of that type. This allows interface code to be written against
+ *   the generic type definition, so it is applicable for all instances.
+ * - Provide a sensible default value
+ * - Enable a semantic interpretation of the variable based on its type
+ *
+ * In the example of this tutorial, we represent a point in 2D space by an array
+ * of double values. The following function adds the corresponding
+ * VariableTypeNode to the hierarchy of variable types.
+ */
+
+#include <signal.h>
+#include "open62541.h"
+
+static UA_NodeId pointTypeId;
+
+static void
+addVariableType2DPoint(UA_Server *server) {
+    UA_VariableTypeAttributes vtAttr;
+    UA_VariableTypeAttributes_init(&vtAttr);
+    vtAttr.dataType = UA_TYPES[UA_TYPES_DOUBLE].typeId;
+    vtAttr.valueRank = 1; /* array with one dimension */
+    UA_UInt32 arrayDims[1] = {2};
+    vtAttr.arrayDimensions = arrayDims;
+    vtAttr.arrayDimensionsSize = 1;
+    vtAttr.displayName = UA_LOCALIZEDTEXT("en_US", "2DPoint Type");
+
+    /* a matching default value is required */
+    UA_Double zero[2] = {0.0, 0.0};
+    UA_Variant_setArray(&vtAttr.value, zero, 2, &UA_TYPES[UA_TYPES_DOUBLE]);
+
+    UA_Server_addVariableTypeNode(server, UA_NODEID_NULL,
+                                  UA_NODEID_NUMERIC(0, UA_NS0ID_BASEVARIABLETYPE),
+                                  UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
+                                  UA_QUALIFIEDNAME(1, "2DPoint Type"), UA_NODEID_NULL,
+                                  vtAttr, NULL, &pointTypeId);
+}
+
+/**
+ * Now the new variable type for *2DPoint* can be referenced during the creation
+ * of a new variable. If no value is given, the default from the variable type
+ * is copied during instantiation.
+ */
+
+static UA_NodeId pointVariableId;
+
+static void
+addVariable(UA_Server *server) {
+    /* Prepare the node attributes */
+    UA_VariableAttributes vAttr;
+    UA_VariableAttributes_init(&vAttr);
+    vAttr.dataType = UA_TYPES[UA_TYPES_DOUBLE].typeId;
+    vAttr.valueRank = 1; /* array with one dimension */
+    UA_UInt32 arrayDims[1] = {2};
+    vAttr.arrayDimensions = arrayDims;
+    vAttr.arrayDimensionsSize = 1;
+    vAttr.displayName = UA_LOCALIZEDTEXT("en_US", "2DPoint Variable");
+    /* vAttr.value is left empty, the server instantiates with the default value */
+
+    /* Add the node */
+    UA_Server_addVariableNode(server, UA_NODEID_NULL,
+                              UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
+                              UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
+                              UA_QUALIFIEDNAME(1, "2DPoint Type"), pointTypeId,
+                              vAttr, NULL, &pointVariableId);
+}
+
+/**
+ * The constraints of the variable type are enforced when creating new variable
+ * instances of the type. In the following function, adding a variable of
+ * *2DPoint* type with a string value fails because The value does not match the
+ * variable type constraints. */
+
+static void
+addVariableFail(UA_Server *server) {
+    /* Prepare the node attributes */
+    UA_VariableAttributes vAttr;
+    UA_VariableAttributes_init(&vAttr);
+    vAttr.dataType = UA_TYPES[UA_TYPES_DOUBLE].typeId;
+    vAttr.valueRank = -1; /* a scalar. this is not allowed per the variable type */
+    vAttr.displayName = UA_LOCALIZEDTEXT("en_US", "2DPoint Variable (fail)");
+    UA_String s = UA_STRING("2dpoint?");
+    UA_Variant_setScalar(&vAttr.value, &s, &UA_TYPES[UA_TYPES_STRING]);
+
+    /* Add the node */
+    UA_Server_addVariableNode(server, UA_NODEID_NULL,
+                              UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
+                              UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
+                              UA_QUALIFIEDNAME(1, "2DPoint Type (fail)"), pointTypeId,
+                              vAttr, NULL, NULL);
+}
+
+/**
+ * The constraints of the variable type are enforced when writing the datatype,
+ * valuerank and arraydimensions attributes of the variable. This, in turn,
+ * constrains the value attribute of the variable. */
+
+static void
+writeVariable(UA_Server *server) {
+    UA_StatusCode retval = UA_Server_writeValueRank(server, pointVariableId, 0);
+    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+                "Setting the Value Rank failed with Status Code %s\n",
+                UA_StatusCode_name(retval));
+
+}
+
+/** It follows the main server code, making use of the above definitions. */
+
+UA_Boolean running = true;
+static void stopHandler(int sign) {
+    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
+    running = false;
+}
+
+int main(void) {
+    signal(SIGINT, stopHandler);
+    signal(SIGTERM, stopHandler);
+
+    UA_ServerConfig config = UA_ServerConfig_standard;
+    UA_ServerNetworkLayer nl =
+        UA_ServerNetworkLayerTCP(UA_ConnectionConfig_standard, 16664);
+    config.networkLayers = &nl;
+    config.networkLayersSize = 1;
+    UA_Server *server = UA_Server_new(config);
+
+    addVariableType2DPoint(server);
+    addVariable(server);
+    addVariableFail(server);
+
+    UA_Server_run(server, &running);
+    UA_Server_delete(server);
+    nl.deleteMembers(&nl);
+    return 0;
+}

+ 0 - 69
examples/tutorials/server_firststeps.c

@@ -1,69 +0,0 @@
-/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
- * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
-
-/**
- * Building a simple server
- * ------------------------
- *
- * This series of tutorial guide you through your first steps with open62541.
- * For compiling the examples, you need a compiler (MS Visual Studio 2015 or
- * newer, GCC, Clang and MinGW32 are all known to be working). The compilation
- * instructions are given for GCC but should be straightforward to adapt.
- *
- * It will also be very helpful to install an OPC UA Client with a graphical
- * frontend, such as UAExpert by Unified Automation. That will enable you to
- * examine the information model of any OPC UA server.
- *
- * To get started, downdload the open62541 single-file release from
- * http://open62541.org or generate it according to the :ref:`build instructions
- * <building>` with the "amalgamation" option enabled. From now on, we assume
- * you have the ``open62541.c/.h`` files in the current folder.
- *
- * Now create a new C source-file called ``myServer.c`` with the following content: */
-
-#include <signal.h>
-#include "open62541.h"
-
-UA_Boolean running = true;
-static void stopHandler(int sig) {
-    running = false;
-}
-
-int main(void) {
-    signal(SIGINT, stopHandler);
-    signal(SIGTERM, stopHandler);
-
-    UA_ServerConfig config = UA_ServerConfig_standard;
-    UA_ServerNetworkLayer nl;
-    nl = UA_ServerNetworkLayerTCP(UA_ConnectionConfig_standard, 4840);
-    config.networkLayers = &nl;
-    config.networkLayersSize = 1;
-    UA_Server *server = UA_Server_new(config);
-
-    UA_Server_run(server, &running);
-    UA_Server_delete(server);
-    nl.deleteMembers(&nl);
-    return 0;
-}
-
-/**
- * This is all that is needed for a simple OPC UA server. Compile the the server
- * with GCC using the following command:
- *
- * .. code-block:: bash
- *
- *    $ gcc -std=c99 open62541.c myServer.c -o myServer
- *
- * Now start the server (and stop with ctrl-c):
- *
- * .. code-block:: bash
- *
- *    $ ./myServer
- *
- * You have now compiled and run your first OPC UA server. You can go ahead and
- * browse the information model with a generic client, such as UAExpert. The
- * server will be listening on ``opc.tcp://localhost:4840`` - go ahead and give
- * it a try.
- *
- * In the following tutorials, you will be shown how to populate the server's
- * information model and how to create a client application. */

+ 1 - 1
include/ua_client_highlevel.h

@@ -28,9 +28,9 @@ extern "C" {
  *
  * Read Attributes
  * ^^^^^^^^^^^^^^^
- *
  * The following functions can be used to retrieve a single node attribute. Use
  * the regular service to read several attributes at once. */
+
 /* Don't call this function, use the typed versions */
 UA_StatusCode UA_EXPORT
 __UA_Client_readAttribute(UA_Client *client, const UA_NodeId *nodeId,

+ 2 - 0
include/ua_server.h

@@ -202,6 +202,8 @@ typedef struct {
 UA_UInt16 UA_EXPORT UA_Server_addNamespace(UA_Server *server, const char* name);
 
 /**
+ * .. _server-lifecycle:
+ *
  * Server Lifecycle
  * ---------------- */
 UA_Server UA_EXPORT * UA_Server_new(const UA_ServerConfig config);

+ 46 - 42
include/ua_types.h

@@ -24,34 +24,16 @@ extern "C" {
  * generated from standard XML definitions. Their exact definitions can be
  * looked up at https://opcfoundation.org/UA/schemas/Opc.Ua.Types.bsd.xml.
  *
- * Note that arrays can only be part of a scalar data type and never constitute
- * a data type on their own. Also, open62541 does not implement unions so far.
- * They are a recent addition to the protocol (since OPC UA v1.03). And so far,
- * no service definition makes of unions in the request / response message
- * definition. Instead, :ref:`Variants <variant>` are used when values of
- * different types are possible.
+ * For users that are new to open62541, take a look at the :ref:`tutorial for
+ * working with data types<types-tutorial>` before diving into the
+ * implementation details.
  *
- * All data types ``T`` (builtin and generated) share the same basic API for
- * creation, copying and deletion:
- *
- * - ``void T_init(T *ptr)``: Initialize the data type. This is synonymous with
- *   zeroing out the memory, i.e. ``memset(ptr, 0, sizeof(T))``.
- * - ``T* T_new()``: Allocate and return the memory for the data type. The
- *   value is already initialized.
- * - ``UA_StatusCode T_copy(const T *src, T *dst)``: Copy the content of the
- *   data type. Returns ``UA_STATUSCODE_GOOD`` or
- *   ``UA_STATUSCODE_BADOUTOFMEMORY``.
- * - ``void T_deleteMembers(T *ptr)``: Delete the dynamically allocated content
- *   of the data type and perform a ``T_init`` to reset the type.
- * - ``void T_delete(T *ptr)``: Delete the content of the data type and the
- *   memory for the data type itself. */
+ * Builtin Types
+ * ------------- */
 
 #define UA_BUILTIN_TYPES_COUNT 25U
 
 /**
- * Builtin Types
- * -------------
- *
  * Boolean
  * ^^^^^^^
  * A two-state logical value (true or false). */
@@ -196,6 +178,8 @@ UA_STRING(char *chars) {
 #define UA_STRING_ALLOC(CHARS) UA_String_fromChars(CHARS)
 
 /**
+ * .. _datetime:
+ *
  * DateTime
  * ^^^^^^^^
  * An instance in time. A DateTime value is encoded as a 64-bit signed integer
@@ -256,8 +240,10 @@ UA_EXPORT extern const UA_Guid UA_GUID_NULL;
 typedef UA_String UA_ByteString;
 
 static UA_INLINE UA_Boolean
-UA_ByteString_equal(const UA_ByteString *string1, const UA_ByteString *string2) {
-    return UA_String_equal((const UA_String*)string1, (const UA_String*)string2);
+UA_ByteString_equal(const UA_ByteString *string1,
+                    const UA_ByteString *string2) {
+    return UA_String_equal((const UA_String*)string1,
+                           (const UA_String*)string2);
 }
 
 /* Allocates memory of size length for the bytestring.
@@ -504,11 +490,11 @@ typedef struct UA_NumericRange UA_NumericRange;
 #define UA_EMPTY_ARRAY_SENTINEL ((void*)0x01)
 
 typedef enum {
-        UA_VARIANT_DATA,          /* The data has the same lifecycle as the
-                                     variant */
-        UA_VARIANT_DATA_NODELETE, /* The data is "borrowed" by the variant and
-                                     shall not be deleted at the end of the
-                                     variant's lifecycle. */
+    UA_VARIANT_DATA,          /* The data has the same lifecycle as the
+                                 variant */
+    UA_VARIANT_DATA_NODELETE, /* The data is "borrowed" by the variant and
+                                 shall not be deleted at the end of the
+                                 variant's lifecycle. */
 } UA_VariantStorageType;
 
 typedef struct {
@@ -650,13 +636,13 @@ UA_Variant_setRangeCopy(UA_Variant *v, const void *array,
  * are described. If the received data type is unkown, the encoded string and
  * target NodeId is stored instead of the decoded value. */
 typedef enum {
-        UA_EXTENSIONOBJECT_ENCODED_NOBODY     = 0,
-        UA_EXTENSIONOBJECT_ENCODED_BYTESTRING = 1,
-        UA_EXTENSIONOBJECT_ENCODED_XML        = 2,
-        UA_EXTENSIONOBJECT_DECODED            = 3,
-        UA_EXTENSIONOBJECT_DECODED_NODELETE   = 4 /* Don't delete the content
-                                                     together with the
-                                                     ExtensionObject */
+    UA_EXTENSIONOBJECT_ENCODED_NOBODY     = 0,
+    UA_EXTENSIONOBJECT_ENCODED_BYTESTRING = 1,
+    UA_EXTENSIONOBJECT_ENCODED_XML        = 2,
+    UA_EXTENSIONOBJECT_DECODED            = 3,
+    UA_EXTENSIONOBJECT_DECODED_NODELETE   = 4 /* Don't delete the content
+                                                 together with the
+                                                 ExtensionObject */
 } UA_ExtensionObjectEncoding;
 
 typedef struct {
@@ -721,10 +707,28 @@ typedef struct UA_DiagnosticInfo {
  *
  * Generic Type Handling
  * ---------------------
- * The builtin types can be combined to data structures. All information about a
- * (structured) data type is stored in a ``UA_DataType``. The array ``UA_TYPES``
- * contains the description of all standard-defined types and is used for
- * handling of generic types. */
+ *
+ * All information about a (builtin/structured) data type is stored in a
+ * ``UA_DataType``. The array ``UA_TYPES`` contains the description of all
+ * standard-defined types. This type description is used for the following
+ * generic operations that work on all types:
+ *
+ * - ``void T_init(T *ptr)``: Initialize the data type. This is synonymous with
+ *   zeroing out the memory, i.e. ``memset(ptr, 0, sizeof(T))``.
+ * - ``T* T_new()``: Allocate and return the memory for the data type. The
+ *   value is already initialized.
+ * - ``UA_StatusCode T_copy(const T *src, T *dst)``: Copy the content of the
+ *   data type. Returns ``UA_STATUSCODE_GOOD`` or
+ *   ``UA_STATUSCODE_BADOUTOFMEMORY``.
+ * - ``void T_deleteMembers(T *ptr)``: Delete the dynamically allocated content
+ *   of the data type and perform a ``T_init`` to reset the type.
+ * - ``void T_delete(T *ptr)``: Delete the content of the data type and the
+ *   memory for the data type itself.
+ *
+ * Specializations, such as ``UA_Int32_new()`` are derived from the generic
+ * type operations as static inline functions.
+ */
+
 typedef struct {
 #ifdef UA_ENABLE_TYPENAMES
     const char *memberName;
@@ -869,7 +873,7 @@ typedef struct {
     UA_UInt32 min;
     UA_UInt32 max;
 } UA_NumericRangeDimension;
-    
+
 struct UA_NumericRange {
     size_t dimensionsSize;
     UA_NumericRangeDimension *dimensions;

+ 8 - 1
src/server/ua_services_attribute.c

@@ -119,9 +119,16 @@ compatibleArrayDimensions(size_t constraintArrayDimensionsSize,
                           const UA_UInt32 *constraintArrayDimensions,
                           size_t testArrayDimensionsSize,
                           const UA_UInt32 *testArrayDimensions) {
+    /* No array dimensions defined -> everything is permitted if the value rank fits */
+    if(constraintArrayDimensionsSize == 0) {
+        return UA_STATUSCODE_GOOD;
+    }
+
+    /* Dimension count must match */
     if(testArrayDimensionsSize != constraintArrayDimensionsSize)
         return UA_STATUSCODE_BADTYPEMISMATCH;
-    /* dimension size zero in the constraint is a wildcard */
+
+    /* Dimension lengths must match; zero in the constraint is a wildcard */
     for(size_t i = 0; i < constraintArrayDimensionsSize; ++i) {
         if(constraintArrayDimensions[i] != testArrayDimensions[i] &&
            constraintArrayDimensions[i] != 0)

+ 76 - 46
src/server/ua_services_nodemanagement.c

@@ -18,8 +18,8 @@ checkParentReference(UA_Server *server, UA_Session *session, UA_NodeClass nodeCl
     /* See if the parent exists */
     const UA_Node *parent = UA_NodeStore_get(server->nodestore, parentNodeId);
     if(!parent) {
-        UA_LOG_DEBUG_SESSION(server->config.logger, session,
-                             "AddNodes: Parent node not found");
+        UA_LOG_INFO_SESSION(server->config.logger, session,
+                            "AddNodes: Parent node not found");
         return UA_STATUSCODE_BADPARENTNODEIDINVALID;
     }
 
@@ -27,22 +27,22 @@ checkParentReference(UA_Server *server, UA_Session *session, UA_NodeClass nodeCl
     const UA_ReferenceTypeNode *referenceType =
         (const UA_ReferenceTypeNode*)UA_NodeStore_get(server->nodestore, referenceTypeId);
     if(!referenceType) {
-        UA_LOG_DEBUG_SESSION(server->config.logger, session,
-                             "AddNodes: Reference type to the parent not found");
+        UA_LOG_INFO_SESSION(server->config.logger, session,
+                            "AddNodes: Reference type to the parent not found");
         return UA_STATUSCODE_BADREFERENCETYPEIDINVALID;
     }
 
     /* Check if the referencetype is a reference type node */
     if(referenceType->nodeClass != UA_NODECLASS_REFERENCETYPE) {
-        UA_LOG_DEBUG_SESSION(server->config.logger, session,
-                             "AddNodes: Reference type to the parent invalid");
+        UA_LOG_INFO_SESSION(server->config.logger, session,
+                            "AddNodes: Reference type to the parent invalid");
         return UA_STATUSCODE_BADREFERENCETYPEIDINVALID;
     }
 
     /* Check that the reference type is not abstract */
     if(referenceType->isAbstract == true) {
-        UA_LOG_DEBUG_SESSION(server->config.logger, session,
-                             "AddNodes: Abstract reference type to the parent invalid");
+        UA_LOG_INFO_SESSION(server->config.logger, session,
+                            "AddNodes: Abstract reference type to the parent not allowed");
         return UA_STATUSCODE_BADREFERENCENOTALLOWED;
     }
 
@@ -54,16 +54,16 @@ checkParentReference(UA_Server *server, UA_Session *session, UA_NodeClass nodeCl
        nodeClass == UA_NODECLASS_REFERENCETYPE) {
         /* type needs hassubtype reference to the supertype */
         if(!UA_NodeId_equal(referenceTypeId, &subtypeId)) {
-            UA_LOG_DEBUG_SESSION(server->config.logger, session,
-                                 "AddNodes: New type node need to have a "
-                                 "hassubtype reference");
+            UA_LOG_INFO_SESSION(server->config.logger, session,
+                                "AddNodes: New type node need to have a "
+                                "HasSubType reference");
             return UA_STATUSCODE_BADREFERENCENOTALLOWED;
         }
         /* supertype needs to be of the same node type  */
         if(parent->nodeClass != nodeClass) {
-            UA_LOG_DEBUG_SESSION(server->config.logger, session,
-                                 "AddNodes: New type node needs to be of the same "
-                                 "node type as the parent");
+            UA_LOG_INFO_SESSION(server->config.logger, session,
+                                "AddNodes: New type node needs to be of the same "
+                                "node type as the parent");
             return UA_STATUSCODE_BADPARENTNODEIDINVALID;
         }
         return UA_STATUSCODE_GOOD;
@@ -74,8 +74,8 @@ checkParentReference(UA_Server *server, UA_Session *session, UA_NodeClass nodeCl
         UA_NODEID_NUMERIC(0, UA_NS0ID_HIERARCHICALREFERENCES);
     if(!isNodeInTree(server->nodestore, referenceTypeId,
                      &hierarchicalReference, &subtypeId, 1)) {
-        UA_LOG_DEBUG_SESSION(server->config.logger, session,
-                             "AddNodes: Reference type is not hierarchical");
+        UA_LOG_INFO_SESSION(server->config.logger, session,
+                            "AddNodes: Reference type is not hierarchical");
         return UA_STATUSCODE_BADREFERENCETYPEIDINVALID;
     }
 
@@ -417,7 +417,7 @@ Service_AddNodes_existing(UA_Server *server, UA_Session *session, UA_Node *node,
 
     /* Check the namespaceindex */
     if(node->nodeId.namespaceIndex >= server->namespacesSize) {
-        UA_LOG_DEBUG_SESSION(server->config.logger, session, "AddNodes: Namespace invalid");
+        UA_LOG_INFO_SESSION(server->config.logger, session, "AddNodes: Namespace invalid");
         UA_NodeStore_deleteNode(node);
         return UA_STATUSCODE_BADNODEIDINVALID;
     }
@@ -426,9 +426,9 @@ Service_AddNodes_existing(UA_Server *server, UA_Session *session, UA_Node *node,
     UA_StatusCode retval = checkParentReference(server, session, node->nodeClass,
                                                 parentNodeId, referenceTypeId);
     if(retval != UA_STATUSCODE_GOOD) {
-        UA_LOG_DEBUG_SESSION(server->config.logger, session,
-                             "AddNodes: Checking the reference to the parent returned"
-                             "error code %s", UA_StatusCode_name(retval));
+        UA_LOG_INFO_SESSION(server->config.logger, session,
+                            "AddNodes: Checking the reference to the parent returned"
+                            "error code %s", UA_StatusCode_name(retval));
         UA_NodeStore_deleteNode(node);
         return retval;
     }
@@ -436,9 +436,9 @@ Service_AddNodes_existing(UA_Server *server, UA_Session *session, UA_Node *node,
     /* Add the node to the nodestore */
     retval = UA_NodeStore_insert(server->nodestore, node);
     if(retval != UA_STATUSCODE_GOOD) {
-        UA_LOG_DEBUG_SESSION(server->config.logger, session,
-                             "AddNodes: Node could not be added to the nodestore "
-                             "with error code %s", UA_StatusCode_name(retval));
+        UA_LOG_INFO_SESSION(server->config.logger, session,
+                            "AddNodes: Node could not be added to the nodestore "
+                            "with error code %s", UA_StatusCode_name(retval));
         return retval;
     }
 
@@ -446,8 +446,8 @@ Service_AddNodes_existing(UA_Server *server, UA_Session *session, UA_Node *node,
     if(addedNodeId) {
         retval = UA_NodeId_copy(&node->nodeId, addedNodeId);
         if(retval != UA_STATUSCODE_GOOD) {
-            UA_LOG_DEBUG_SESSION(server->config.logger, session,
-                                 "AddNodes: Could not copy the nodeid");
+            UA_LOG_INFO_SESSION(server->config.logger, session,
+                                "AddNodes: Could not copy the nodeid");
             goto remove_node;
         }
     }
@@ -461,9 +461,9 @@ Service_AddNodes_existing(UA_Server *server, UA_Session *session, UA_Node *node,
     item.targetNodeId.nodeId = *parentNodeId;
     retval = Service_AddReferences_single(server, session, &item);
     if(retval != UA_STATUSCODE_GOOD) {
-        UA_LOG_DEBUG_SESSION(server->config.logger, session,
-                             "AddNodes: Could not add the reference to the parent"
-                             "with error code %s", UA_StatusCode_name(retval));
+        UA_LOG_INFO_SESSION(server->config.logger, session,
+                            "AddNodes: Could not add the reference to the parent"
+                            "with error code %s", UA_StatusCode_name(retval));
         goto remove_node;
     }
 
@@ -483,9 +483,9 @@ Service_AddNodes_existing(UA_Server *server, UA_Session *session, UA_Node *node,
         retval = instantiateNode(server, session, &node->nodeId, node->nodeClass,
                                  typeDefinition, instantiationCallback);
         if(retval != UA_STATUSCODE_GOOD) {
-            UA_LOG_DEBUG_SESSION(server->config.logger, session,
-                                 "AddNodes: Could not instantiate the node with"
-                                 "error code 0x%08x", retval);
+            UA_LOG_INFO_SESSION(server->config.logger, session,
+                                "AddNodes: Could not instantiate the node with"
+                                "error code %s", UA_StatusCode_name(retval));
             goto remove_node;
         }
     }
@@ -531,9 +531,9 @@ copyCommonVariableAttributes(UA_Server *server, UA_VariableNode *node,
     UA_StatusCode retval = UA_STATUSCODE_GOOD;
     if(UA_NodeId_equal(&node->nodeId, &basevartype) || 
        UA_NodeId_equal(&node->nodeId, &basedatavartype)) {
-      node->dataType = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATATYPE);
-      node->valueRank = -2;
-      return retval;
+        node->dataType = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATATYPE);
+        node->valueRank = -2;
+        return retval;
     }
     
     const UA_VariableTypeNode *vt =
@@ -552,14 +552,18 @@ copyCommonVariableAttributes(UA_Server *server, UA_VariableNode *node,
         return retval;
         
     /* Set the array dimensions. Check only against the vt. */
-    retval = compatibleArrayDimensions(attr->arrayDimensionsSize, attr->arrayDimensions,
-                                       vt->arrayDimensionsSize, vt->arrayDimensions);
-    if(retval != UA_STATUSCODE_GOOD)
-        return retval;
-    retval = UA_Array_copy(attr->arrayDimensions, attr->arrayDimensionsSize,
-                           (void**)&node->arrayDimensions, &UA_TYPES[UA_TYPES_UINT32]);
-    if(retval != UA_STATUSCODE_GOOD)
+    retval = compatibleArrayDimensions(vt->arrayDimensionsSize, vt->arrayDimensions,
+                                       attr->arrayDimensionsSize, attr->arrayDimensions);
+    if(retval == UA_STATUSCODE_GOOD) {
+        retval = UA_Array_copy(attr->arrayDimensions, attr->arrayDimensionsSize,
+                               (void**)&node->arrayDimensions, &UA_TYPES[UA_TYPES_UINT32]);
+    }
+    if(retval != UA_STATUSCODE_GOOD) {
+        UA_LOG_INFO(server->config.logger, UA_LOGCATEGORY_SERVER,
+                    "Array dimensions incompatible with the VariableType "
+                    "with error code %s", UA_StatusCode_name(retval));
         return retval;
+    }
     node->arrayDimensionsSize = attr->arrayDimensionsSize;
 
     /* Set the valuerank */
@@ -567,15 +571,32 @@ copyCommonVariableAttributes(UA_Server *server, UA_VariableNode *node,
         retval = writeValueRankAttribute(server, node, attr->valueRank, vt->valueRank);
     else /* workaround common error where the valuerank is left as 0 */
         node->valueRank = vt->valueRank;
-    if(retval != UA_STATUSCODE_GOOD)
+    if(retval != UA_STATUSCODE_GOOD) {
+        UA_LOG_INFO(server->config.logger, UA_LOGCATEGORY_SERVER,
+                    "Value Rank incompatible with the VariableType "
+                    "with error code %s", UA_StatusCode_name(retval));
         return retval;
-    
+    }
+
     /* Set the value */
     UA_DataValue value;
     UA_DataValue_init(&value);
-    value.value = attr->value;
     value.hasValue = true;
-    retval |= writeValueAttribute(server, node, &value, NULL);
+    value.value = attr->value;
+    value.value.storageType = UA_VARIANT_DATA_NODELETE;
+
+    /* Use the default value from the vt if none is defined */
+    if(!value.value.type)
+        readValueAttribute(server, (const UA_VariableNode *)vt, &value);
+
+    /* Write the value to the node */
+    retval = writeValueAttribute(server, node, &value, NULL);
+    if(retval != UA_STATUSCODE_GOOD) {
+        UA_LOG_INFO(server->config.logger, UA_LOGCATEGORY_SERVER,
+                    "Could not set the value of the new node "
+                    "with error code %s", UA_StatusCode_name(retval));
+    }
+    UA_DataValue_deleteMembers(&value);
     return retval;
 }
 
@@ -704,14 +725,23 @@ Service_AddNodes_single(UA_Server *server, UA_Session *session,
     /* Create the node from the attributes*/
     UA_Node *node = NULL;
     result->statusCode = createNodeFromAttributes(server, item, &node);
-    if(result->statusCode != UA_STATUSCODE_GOOD)
+    if(result->statusCode != UA_STATUSCODE_GOOD) {
+        UA_LOG_INFO_SESSION(server->config.logger, session,
+                            "Could not add node with error code %s",
+                            UA_StatusCode_name(result->statusCode));
         return;
+    }
 
     /* Run consistency checks and add the node */
     UA_assert(node != NULL);
     result->statusCode = Service_AddNodes_existing(server, session, node, &item->parentNodeId.nodeId,
                                                    &item->referenceTypeId, &item->typeDefinition.nodeId,
                                                    instantiationCallback, &result->addedNodeId);
+    if(result->statusCode != UA_STATUSCODE_GOOD) {
+        UA_LOG_INFO_SESSION(server->config.logger, session,
+                            "Could not add node with error code %s",
+                            UA_StatusCode_name(result->statusCode));
+    }
 }
 
 void Service_AddNodes(UA_Server *server, UA_Session *session,

+ 7 - 11
tools/travis/travis_linux_after_success.sh

@@ -1,17 +1,13 @@
 #!/bin/bash
 set -ev
 
-if [ $ANALYZE = "true" ]; then
-    echo "=== Skipping after_success scripts in ANALYZE mode ==="
-else
+if [ "$ANALYZE" = "false" ] && [ "$CC" = "gcc" ] && [ "${TRAVIS_REPO_SLUG}" = "open62541/open62541" ]; then
     echo "=== Executing after_success scripts ==="
-    if [ "$CC" = "gcc" ] && [ "${TRAVIS_REPO_SLUG}" = "open62541/open62541" ]; then
-        if [ ${TRAVIS_PULL_REQUEST} == "false" ] && [ ${TRAVIS_BRANCH} = "0.2" ]; then
-            sh ./tools/travis/travis_push_doc.sh
-            sh ./tools/travis/travis_push_coverity.sh
-        fi
-        sh ./tools/travis/travis_push_release.sh;
-    else
-        echo "  Skipping push scripts since not gcc and/or ${TRAVIS_REPO_SLUG} is not the main repo"
+    if [ "${TRAVIS_PULL_REQUEST}" = "false" ] && [ "${TRAVIS_BRANCH}" = "0.2" ]; then
+        sh ./tools/travis/travis_push_doc.sh
+        sh ./tools/travis/travis_push_coverity.sh
     fi
+    sh ./tools/travis/travis_push_release.sh;
+else
+    echo "=== Not in the main repository or not the main build; Skip release scripts ==="
 fi

+ 1 - 1
tools/travis/travis_push_release.sh

@@ -10,7 +10,6 @@ COMMENT="$(git log --pretty=format:"%s" --date=iso --abbrev=10 --all -1)"
 git clone --depth=5 -b gh-pages https://$GITAUTH@github.com/open62541/open62541-www
 cd open62541-www
 
-#hanndle releases
 cd releases
 if [ ! -e "$TAG.zip" ]; then
     #add the first line
@@ -48,5 +47,6 @@ git config --global user.name "Open62541 travis-ci"
 git config --global push.default simple
 git commit -am "added release files and updated releases webpage by travis-ci [ci skip]"
 git push https://$GITAUTH@github.com/open62541/open62541-www
+
 cd ..
 rm -rf open62541-www