Browse Source

adding tutorials for variable nodes and methods

Stasik0 8 years ago
parent
commit
55eaf1c6c4

+ 2 - 47
doc/tutorial_firstStepsClient.rst

@@ -61,53 +61,6 @@ Let's recompile both server and client - if you feel up to it, you can create a
 
     :myApp> gcc -Wl,-rpath=./ -L./ -I ./include -o myClient myClient.c  -lopen62541
 
-We will also make a slight change to our server: We want it to exit cleanly when pressing ``CTRL+C``. We will add signal handler for SIGINT and SIGTERM to accomplish that to the server::
-
-    #include <stdio.h>
-    #include <signal.h>
-
-    # include "ua_types.h"
-    # include "ua_server.h"
-    # include "logger_stdout.h"
-    # include "networklayer_tcp.h"
-
-    UA_Boolean running;
-    UA_Logger logger;
-
-    void stopHandler(int signal) {
-      running = 0;
-    }
-
-    int main(void) {
-      signal(SIGINT,  stopHandler);
-      signal(SIGTERM, stopHandler);
-      
-      UA_Server *server = UA_Server_new(UA_ServerConfig_standard);
-      logger = Logger_Stdout_new();
-      UA_Server_setLogger(server, logger);
-      UA_Server_addNetworkLayer(server, ServerNetworkLayerTCP_new(UA_ConnectionConfig_standard, 16664));
-      running = UA_TRUE;
-      UA_Server_run(server, 1, &running);
-      UA_Server_delete(server);
-      
-      printf("Terminated\n");
-      return 0;
-    }
-And then of course, recompile it::
-
-    :myApp> gcc -Wl,-rpath=./ -L./ -I ./include -o myServer myServer.c  -lopen62541
-
-You can now start and background the server, run the client, and then terminate the server like so::
-
-    :myApp> ./myServer &
-    [xx/yy/zz aa:bb:cc.dd.ee] info/communication	Listening on opc.tcp://localhost:16664
-    [1] 2114
-    :myApp> ./myClient && killall myServer
-    Terminated
-    [1]+  Done                    ./myServer
-    :myApp> 
-
-Notice how the server received the SIGTERM signal from kill and exited cleany? We also used the return value of our client by inserting the ``&&``, so kill is only called after a clean client exit (``return 0``).
 
 Asserting success/failure
 -------------------------
@@ -264,6 +217,8 @@ As the last step for this tutorial, we are going to convert the raw date value i
       UA_Client_delete(client);
       return 0;
     }
+
+Note that this file can be found as "examples/client_firstSteps.c" in the repository.
     
 Now you should see raw time and a formatted date::
 

+ 51 - 0
doc/tutorial_firstStepsServer.rst

@@ -156,6 +156,57 @@ Now execute the server::
 
 You have now compiled and started your first OPC UA Server. Though quite unspectacular and only terminatable with ``CTRL+C`` (SIGTERM) at the moment, you can already launch it and browse around with UA Expert. The Server will be listening on localhost:16664 - go ahead and give it a try.
 
+We will also make a slight change to our server: We want it to exit cleanly when pressing ``CTRL+C``. We will add signal handler for SIGINT and SIGTERM to accomplish that to the server::
+
+    #include <stdio.h>
+    #include <signal.h>
+
+    # include "ua_types.h"
+    # include "ua_server.h"
+    # include "logger_stdout.h"
+    # include "networklayer_tcp.h"
+
+    UA_Boolean running;
+    UA_Logger logger;
+
+    static void stopHandler(int signal) {
+      running = UA_FALSE;
+    }
+
+    int main(void) {
+      signal(SIGINT,  stopHandler);
+      signal(SIGTERM, stopHandler);
+      
+      UA_Server *server = UA_Server_new(UA_ServerConfig_standard);
+      logger = Logger_Stdout_new();
+      UA_Server_setLogger(server, logger);
+      UA_Server_addNetworkLayer(server, ServerNetworkLayerTCP_new(UA_ConnectionConfig_standard, 16664));
+      running = UA_TRUE;
+      UA_Server_run(server, 1, &running);
+      UA_Server_delete(server);
+      
+      printf("Terminated\n");
+      return 0;
+    }
+
+Note that this file can be found as "examples/server_firstSteps.c" in the repository.
+    
+And then of course, recompile it::
+
+    :myApp> gcc -Wl,-rpath=./ -L./ -I ./include -o myServer myServer.c  -lopen62541
+
+You can now start and background the server, run the client, and then terminate the server like so::
+
+    :myApp> ./myServer &
+    [xx/yy/zz aa:bb:cc.dd.ee] info/communication	Listening on opc.tcp://localhost:16664
+    [1] 2114
+    :myApp> ./myClient && killall myServer
+    Terminated
+    [1]+  Done                    ./myServer
+    :myApp> 
+
+Notice how the server received the SIGTERM signal from kill and exited cleany? We also used the return value of our client by inserting the ``&&``, so kill is only called after a clean client exit (``return 0``).
+
 Introduction to Configuration options (Amalgamation)
 ----------------------------------------------------
 

+ 8 - 0
doc/tutorial_server_method.rst

@@ -0,0 +1,8 @@
+Adding server-side methods
+======================
+
+This tutorial demonstrates how to add method nodes to the server.
+
+Consider 'examples/server_method.c' in the repository. The examples are compiled if the Cmake option BUILD_EXAMPLE is turned on.
+
+Use an UA client, e.g., UaExpert to call the method (right-click on the method node -> call).

+ 57 - 0
doc/tutorial_server_variables.rst

@@ -0,0 +1,57 @@
+Adding nodes to a server and connecting nodes to user-defined values
+======================
+
+This tutorial shows how to add variable nodes to a server and how these can be connected to user-defined values and callbacks.
+
+Firstly, we need to introduce a concept of Variants. This is a data structure able to hold any datatype.
+
+Variants
+---------------------
+The datatype UA_Variant a belongs to the built-in datatypes of OPC UA and is used as a container type. A Variant can hold any other built-in scalar datatype (except Variants) or array built-in datatype (array of variants too). The variant is structured like this in open62541::
+
+	typedef struct {
+		const UA_DataType *type; ///< The nodeid of the datatype
+		enum {
+		    UA_VARIANT_DATA, ///< The data is "owned" by this variant (copied and deleted together)
+		    UA_VARIANT_DATA_NODELETE, /**< The data is "borrowed" by the variant and shall not be
+		                                   deleted at the end of this variant's lifecycle. It is not
+		                                   possible to overwrite borrowed data due to concurrent access.
+		                                   Use a custom datasource with a mutex. */
+		} storageType; ///< Shall the data be deleted together with the variant
+		UA_Int32  arrayLength;  ///< the number of elements in the data-pointer
+		void     *data; ///< points to the scalar or array data
+		UA_Int32  arrayDimensionsSize; ///< the number of dimensions the data-array has
+		UA_Int32 *arrayDimensions; ///< the length of each dimension of the data-array
+	} UA_Variant;
+
+The members of the struct are
+
+* type: a pointer to the vtable entry. It points onto the functions which handles the stored data i.e. encode/decode etc.
+
+* storageType:  used to declare who the owner of data is and to which lifecycle it belongs. Three different cases are possible:
+
+ * UA_VARIANT_DATA: this is the simplest case. The data belongs to the variant, which means if the variant is deleted so does the data which is kept inside
+ 
+ * UA_VARIANT_DATASOURCE: in this case user-defined functions are called to access the data. The signature of the functions is defined by UA_VariantDataSource structure. A use-case could be to access a sensor only when the data is asked by some client
+
+* arrayLength: length of the array (-1 if a scalar is saved)
+
+* data: raw pointer to the saved vale or callback
+
+* arrayDimensionsSize: size of arrayDimensions array
+
+* arrayDimensions: dimensinos array in case the array is interpreted as a multi-dimensional construction, e.g., [5,5] for a 5x5 matrix
+
+Adding a variable node to the server that contains a user-defined variable
+---------------------
+
+This simple case allows to 'inject' a pre-defined variable into a variable node. The variable is wrapped by a "UA_Variant" before being insterted into the node.
+
+Consider 'examples/server_variable.c' in the repository. The examples are compiled if the Cmake option BUILD_EXAMPLE is turned on.
+
+Adding a variable node to the server that contains a user-defined callback.
+---------------------
+
+The latter case allows to define callback functions that are executed on read or write of the node. In this case an "UA_DataSource" containing the respective callback pointer is intserted into the node.
+
+Consider 'examples/server_datasource.c' in the repository. The examples are compiled if the Cmake option BUILD_EXAMPLE is turned on.

+ 26 - 2
doc/tutorials.rst

@@ -4,7 +4,7 @@ Tutorials
 Tutorial 1: First steps with open62541-server
 ------------------------------
 
-:doc:`tutorial_firstStepsServer`
+:doc:`tutorial_server_firstSteps`
 
 Contents:
 
@@ -19,7 +19,7 @@ Contents:
 Tutorial 2: First steps with open62541-client
 ------------------------------
 
-:doc:`tutorial_firstStepsClient`
+:doc:`tutorial_server_firstSteps`
 
 Contents:
 
@@ -32,3 +32,27 @@ Contents:
 * Reading a variable
 
 * Introduction to stings
+
+Tutorial 3: Adding nodes to a server and connecting nodes to user-defined values
+------------------------------
+
+:doc:`tutorial_server_variables`
+
+Contents:
+
+* Intorduction to Variants
+
+* Adding user-defined nodes to a server
+
+* Connecting node to a variable
+
+* Connecting node to a callback function
+
+Tutorial 4: Adding server-side methods
+------------------------------
+
+:doc:`tutorial_server_method`
+
+Contents:
+
+* Defining server-side method nodes

+ 7 - 0
examples/CMakeLists.txt

@@ -20,6 +20,13 @@ target_link_libraries(server_variable ${LIBS})
 add_executable(server_datasource server_datasource.c)
 target_link_libraries(server_datasource ${LIBS})
 
+add_executable(server_firstSteps server_firstSteps.c)
+target_link_libraries(server_firstSteps ${LIBS})
+
+add_executable(client_firstSteps client_firstSteps.c)
+target_link_libraries(client_firstSteps ${LIBS})
+
+
 add_executable(server_repeated_job server_repeated_job.c)
 target_link_libraries(server_repeated_job ${LIBS})
 

+ 48 - 0
examples/client_firstSteps.c

@@ -0,0 +1,48 @@
+//This file contains source-code that is discussed in a tutorial located here:
+//http://open62541.org/doc/sphinx/tutorial_firstStepsClient.html
+
+#include <stdio.h>
+
+#include "ua_types.h"
+#include "ua_server.h"
+#include "logger_stdout.h"
+#include "networklayer_tcp.h"
+
+int main(void) {
+    UA_Client *client = UA_Client_new(UA_ClientConfig_standard, Logger_Stdout_new());
+    UA_StatusCode retval = UA_Client_connect(client, ClientNetworkLayerTCP_connect, "opc.tcp://localhost:16664");
+    if(retval != UA_STATUSCODE_GOOD) {
+        UA_Client_delete(client);
+        return retval;
+    }
+
+    //variables to store data
+    UA_DateTime raw_date = 0;
+    UA_String* string_date = UA_String_new();
+
+    UA_ReadRequest rReq;
+    UA_ReadRequest_init(&rReq);
+    rReq.nodesToRead = UA_Array_new(&UA_TYPES[UA_TYPES_READVALUEID], 1);
+    rReq.nodesToReadSize = 1;
+    rReq.nodesToRead[0].nodeId = UA_NODEID_NUMERIC(0, 2258);
+    rReq.nodesToRead[0].attributeId = UA_ATTRIBUTEID_VALUE;
+
+    UA_ReadResponse rResp = UA_Client_read(client, &rReq);
+    if(rResp.responseHeader.serviceResult == UA_STATUSCODE_GOOD &&
+            rResp.resultsSize > 0 && rResp.results[0].hasValue &&
+            UA_Variant_isScalar(&rResp.results[0].value) &&
+            rResp.results[0].value.type == &UA_TYPES[UA_TYPES_DATETIME]) {
+        raw_date = *(UA_DateTime*)rResp.results[0].value.data;
+        printf("raw date is: %llu\n", raw_date);
+        UA_DateTime_toString(raw_date, string_date);
+        printf("string date is: %.*s\n", string_date->length, string_date->data);
+    }
+
+    UA_ReadRequest_deleteMembers(&rReq);
+    UA_ReadResponse_deleteMembers(&rResp);
+    UA_String_delete(string_date);
+
+    UA_Client_disconnect(client);
+    UA_Client_delete(client);
+    return 0;
+}

+ 3 - 3
examples/server.c

@@ -197,11 +197,11 @@ static UA_ByteString loadCertificate(void) {
 
 UA_StatusCode nodeIter(UA_NodeId childId, UA_Boolean isInverse, UA_NodeId referenceTypeId, void *handle);
 UA_StatusCode nodeIter(UA_NodeId childId, UA_Boolean isInverse, UA_NodeId referenceTypeId, void *handle) {  
-  printf("References ns=%d;i=%d using i=%d ", childId.namespaceIndex, childId.identifier.numeric, referenceTypeId.identifier.numeric);
+  /*printf("References ns=%d;i=%d using i=%d ", childId.namespaceIndex, childId.identifier.numeric, referenceTypeId.identifier.numeric);
   if (isInverse == UA_TRUE) {
     printf(" (inverse)");
   }
-  printf("\n");
+  printf("\n");*/
   
   return UA_STATUSCODE_GOOD;
 }
@@ -394,7 +394,7 @@ int main(int argc, char** argv) {
 #endif
    
   // Example for iterating over all nodes referenced by "Objects":
-  printf("Nodes connected to 'Objects':\n=============================\n");
+  //printf("Nodes connected to 'Objects':\n=============================\n");
   UA_Server_forEachChildNodeCall(server, UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), nodeIter, NULL);
   
   // Some easy localization

+ 33 - 0
examples/server_firstSteps.c

@@ -0,0 +1,33 @@
+//This file contains source-code that is discussed in a tutorial located here:
+// http://open62541.org/doc/sphinx/tutorial_firstStepsServer.html
+
+#include <stdio.h>
+#include <signal.h>
+
+# include "ua_types.h"
+# include "ua_server.h"
+# include "logger_stdout.h"
+# include "networklayer_tcp.h"
+
+UA_Boolean running;
+UA_Logger logger;
+
+static void stopHandler(int signal) {
+    running = UA_FALSE;
+}
+
+int main(void) {
+    signal(SIGINT,  stopHandler);
+    signal(SIGTERM, stopHandler);
+
+    UA_Server *server = UA_Server_new(UA_ServerConfig_standard);
+    logger = Logger_Stdout_new();
+    UA_Server_setLogger(server, logger);
+    UA_Server_addNetworkLayer(server, ServerNetworkLayerTCP_new(UA_ConnectionConfig_standard, 16664));
+    running = UA_TRUE;
+    UA_Server_run(server, 1, &running);
+    UA_Server_delete(server);
+
+    printf("Terminated\n");
+    return 0;
+}

+ 11 - 6
examples/server_method.c

@@ -32,13 +32,13 @@ static UA_StatusCode helloWorldMethod(const UA_NodeId objectId, const UA_Variant
         return UA_STATUSCODE_GOOD;
 } 
 
-static UA_StatusCode IncInt32ArrayValues(const UA_NodeId objectId,
+static UA_StatusCode IncInt32ArrayValuesMethod(const UA_NodeId objectId,
                                          const UA_Variant *input, UA_Variant *output, void *handle) {
 
 
 	UA_Variant_setArrayCopy(output,input->data,5,&UA_TYPES[UA_TYPES_INT32]);
 	for(int i = 0; i< input->arrayLength; i++){
-		((UA_Int32*)output->data)[i] = ((UA_Int32*)input->data)[i] + 2;
+		((UA_Int32*)output->data)[i] = ((UA_Int32*)input->data)[i] + 1;
 	}
 
 	return UA_STATUSCODE_GOOD;
@@ -57,6 +57,7 @@ int main(int argc, char** argv) {
     UA_Server_setLogger(server, logger);
     UA_Server_addNetworkLayer(server, ServerNetworkLayerTCP_new(UA_ConnectionConfig_standard, 16664));
 
+    //EXAMPLE 1
     /* add the method node with the callback */
     UA_Argument inputArguments;
     UA_Argument_init(&inputArguments);
@@ -78,9 +79,12 @@ int main(int argc, char** argv) {
         
     UA_Server_addMethodNode(server, UA_NODEID_NUMERIC(1,62541), UA_QUALIFIEDNAME(1, "hello world"), 
                             UA_LOCALIZEDTEXT("en_US","Hello World"), UA_LOCALIZEDTEXT("en_US","Say `Hello World`"),
-                            UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
+                            UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
                             0, 0, &helloWorldMethod, NULL, 1, &inputArguments, 1, &outputArguments, NULL);
 
+    //END OF EXAMPLE 1
+
+    //EXAMPLE 2
     /* add another method node: output argument as 1d Int32 array*/
     // define input arguments
     UA_Argument_init(&inputArguments);
@@ -104,18 +108,19 @@ int main(int argc, char** argv) {
     outputArguments.description = UA_LOCALIZEDTEXT("en_US",
                     "increment each array index");
     outputArguments.name = UA_STRING(
-                    "output is the array, each index is incremented");
+                    "output is the array, each index is incremented by one");
     outputArguments.valueRank = 1;
 
     UA_Server_addMethodNode(server, UA_NODEID_STRING(1, "IncInt32ArrayValues"), UA_QUALIFIEDNAME(1, "IncInt32ArrayValues"),
                             UA_LOCALIZEDTEXT("en_US","1dArrayExample"), UA_LOCALIZEDTEXT("en_US","1dArrayExample"),
                             UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), 
-                            0, 0, &IncInt32ArrayValues, NULL, 1, &inputArguments, 1, &outputArguments, NULL);
+                            0, 0, &IncInt32ArrayValuesMethod, NULL, 1, &inputArguments, 1, &outputArguments, NULL);
+    //END OF EXAMPLE 2
 
     /* start server */
     UA_StatusCode retval = UA_Server_run(server, 1, &running); //blocks until running=false
 
-        /* ctrl-c received -> clean up */
+    /* ctrl-c received -> clean up */
     UA_UInt32_delete(pInputDimensions);
     UA_UInt32_delete(pOutputDimensions);
     UA_Server_delete(server);