Browse Source

finished client first steps tutorial with reading nodes's values

Stasik0 9 years ago
parent
commit
5cbad447af
3 changed files with 206 additions and 246 deletions
  1. 161 9
      doc/tutorial_firstStepsClient.rst
  2. 6 2
      doc/tutorials.rst
  3. 39 235
      examples/client.c

+ 161 - 9
doc/tutorial_firstStepsClient.rst

@@ -9,9 +9,9 @@ You should already have a basic server from the previous tutorial. open62541 pro
 
 
 As a recap, your directory structure should now look like this::
 As a recap, your directory structure should now look like this::
  
  
-  :myServerApp> rm *.o open62541.*
-  :myServerApp> ln -s ../open62541/build/*so ./
-  :myServerApp> tree
+  :myApp> rm *.o open62541.*
+  :myApp> ln -s ../open62541/build/*so ./
+  :myApp> tree
   .
   .
   ├── include
   ├── include
   │   ├── logger_stdout.h
   │   ├── logger_stdout.h
@@ -35,7 +35,7 @@ As a recap, your directory structure should now look like this::
 
 
 Note that I have linked the library into the folder to spare me the trouble of copying it every time I change/rebuild the stack.
 Note that I have linked the library into the folder to spare me the trouble of copying it every time I change/rebuild the stack.
 
 
-To create a really basic client, navigate back into the MyServerApp folder from the previous tutorial and create a client::
+To create a really basic client, navigate back into the myApp folder from the previous tutorial and create a client::
 
 
     #include <stdio.h>
     #include <stdio.h>
 
 
@@ -59,7 +59,7 @@ To create a really basic client, navigate back into the MyServerApp folder from
 
 
 Let's recompile both server and client - if you feel up to it, you can create a Makefile for this procedure. I will show a final command line compile example and ommit the compilation directives in future examples.::
 Let's recompile both server and client - if you feel up to it, you can create a Makefile for this procedure. I will show a final command line compile example and ommit the compilation directives in future examples.::
 
 
-    :myServerApp> gcc -Wl,-rpath=./ -L./ -I ./include -o myClient myClient.c  -lopen62541
+    :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::
 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::
 
 
@@ -95,17 +95,17 @@ We will also make a slight change to our server: We want it to exit cleanly when
     }
     }
 And then of course, recompile it::
 And then of course, recompile it::
 
 
-    :myServerApp> gcc -Wl,-rpath=./ -L./ -I ./include -o myServer myServer.c  -lopen62541
+    :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::
 You can now start and background the server, run the client, and then terminate the server like so::
 
 
-    :myServerApp> ./myServer &
+    :myApp> ./myServer &
     [xx/yy/zz aa:bb:cc.dd.ee] info/communication	Listening on opc.tcp://localhost:16664
     [xx/yy/zz aa:bb:cc.dd.ee] info/communication	Listening on opc.tcp://localhost:16664
     [1] 2114
     [1] 2114
-    :myServerApp> ./myClient && killall myServer
+    :myApp> ./myClient && killall myServer
     Terminated
     Terminated
     [1]+  Done                    ./myServer
     [1]+  Done                    ./myServer
-    :myServerApp> 
+    :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``).
 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``).
 
 
@@ -114,5 +114,157 @@ Asserting success/failure
 
 
 Almost all functions of the open62541 API will return a ``UA_StatusCode``, which in the C world would be represented by a ``unsigned int``. OPC UA defines large number of good and bad return codes represented by this number. The constant UA_STATUSCODE_GOOD is defined as 0 in ``include/ua_statuscodes.h`` along with many other return codes. It pays off to check the return code of your function calls, as we already did implicitly in the client.
 Almost all functions of the open62541 API will return a ``UA_StatusCode``, which in the C world would be represented by a ``unsigned int``. OPC UA defines large number of good and bad return codes represented by this number. The constant UA_STATUSCODE_GOOD is defined as 0 in ``include/ua_statuscodes.h`` along with many other return codes. It pays off to check the return code of your function calls, as we already did implicitly in the client.
 
 
+Minimalistic introduction to OPC UA nodes and node IDs
+-----------------------
+OPC UA nodespace model defines 9 standard attribute for every node:
 
 
++---------------+----------------+
+| Type          | Name           |
++===============+================+
+| NodeId        | nodeID         |
++---------------+----------------+
+| NodeClass     | nodeClass      |
++---------------+----------------+
+| QualifiedName | browseName     |
++---------------+----------------+
+| LocalizedText | displayName    |
++---------------+----------------+
+| LocalizedText | description    |
++---------------+----------------+
+| UInt32        | writeMask      |
++---------------+----------------+
+| UInt32        | userWriteMask  |
++---------------+----------------+
+| Int32         | referencesSize |
++---------------+----------------+
+|ReferenceNode[]| references     |
++---------------+----------------+
 
 
+Furthermore, there are different node types that are stored in NodeClass. 
+For different classes, nodes have additional properties.
+
+In this tutorial we are interested in one of these types: "Variable". In this case a node will have an additional attribute called "value" which we are going to read.
+
+Let us go on with node IDs. A node ID is a unique identifier in server's context. It is composed of two members:
+
++-------------+-----------------+---------------------------+
+| Type        | Name            | Notes                     |
++=============+=================+===========================+
+| UInt16      | namespaceIndex  |  Number of the namespace  |
++-------------+-----------------+---------------------------+
+| Union       | identifier      |  One idenifier of the     |
+|             |  * String       |  listed types             |
+|             |  * Integer      |                           |
+|             |  * GUID         |                           |
+|             |  * ByteString   |                           |
++-------------+-----------------+---------------------------+
+
+The first parameter is the number of node's namespace, the second one may be a numeric, a string or a GUID (Globally Unique ID) identifier. 
+
+Reading variable's node value
+-----------------------------
+
+In this example we are going to read node (n=0,i=2258), i.e. a node in namespace 0 with a numerical id 2258. This node is present in every server (since it is located in namespace 0) and contains server current time (encoded as UInt64).
+
+Let us extend the client with with an action reading node's value::
+
+    #include <stdio.h>
+
+    #include "ua_types.h"
+    #include "ua_server.h"
+    #include "logger_stdout.h"
+    #include "networklayer_tcp.h"
+
+    int main(void) {
+      //variable to store data
+      UA_DateTime raw_date = 0;
+
+      UA_ReadRequest rReq;
+      UA_ReadRequest_init(&rReq);
+      rReq.nodesToRead = UA_ReadValueId_new();
+      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_ReadRequest_deleteMembers(&rReq);
+      UA_ReadResponse_deleteMembers(&rResp);
+
+      UA_Client_disconnect(client);
+      UA_Client_delete(client);
+      return 0;
+    } 
+
+You should see raw time in milliseconds since January 1, 1601 UTC midnight::
+
+    :myApp> ./myClient
+    :myApp> raw date is: 130856974061125520
+    
+Firstly we constructed a read request "rReq", it contains 1 node's attribute we want to query for. The attribute is filled with the numeric id "UA_NODEID_NUMERIC(0, 2258)" and the attribute we are reading "UA_ATTRIBUTEID_VALUE". After the read request was sent, we can find the actual read value in the read response.
+
+As the last step for this tutorial, we are going to convert the raw date value into a well formatted string::
+
+    #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;
+    }
+    
+Now you should see raw time and a formatted date::
+
+    :myApp> ./myClient
+    :myApp> raw date is: 130856981449041870
+            string date is: 09/02/2015 20:09:04.904.187.000
+
+Further tasks
+-------------
+* Display the value of the variable node (ns=1,i="the.answer") containing an "Int32" from the example server (which is built in :doc:`tutorial_firstStepsServer`). Note that the identifier of this node is a string type: use "UA_NODEID_STRING_ALLOC". The answer can be found in "examples/exampleClient.c".
+* Try to set the value of the variable node (ns=1,i="the.answer") containing an "Int32" from the example server (which is built in :doc:`tutorial_firstStepsServer`) using "UA_Client_write" function. The example server needs some more modifications, i.e., changing request types. The answer can be found in "examples/exampleClient.c".

+ 6 - 2
doc/tutorials.rst

@@ -1,7 +1,7 @@
 Tutorials
 Tutorials
 =========
 =========
 
 
-First steps with open62541-server
+Tutorial 1: First steps with open62541-server
 ------------------------------
 ------------------------------
 
 
 :doc:`tutorial_firstStepsServer`
 :doc:`tutorial_firstStepsServer`
@@ -16,7 +16,7 @@ Contents:
 
 
 * Compiling built-in server and client examples
 * Compiling built-in server and client examples
 
 
-First steps with open62541-client
+Tutorial 2: First steps with open62541-client
 ------------------------------
 ------------------------------
 
 
 :doc:`tutorial_firstStepsClient`
 :doc:`tutorial_firstStepsClient`
@@ -27,4 +27,8 @@ Contents:
 
 
 * Creating a minimal client
 * Creating a minimal client
 
 
+* Minimalistic introduction to OPC UA nodes and node IDs
+
 * Reading a variable
 * Reading a variable
+
+* Introduction to stings

+ 39 - 235
examples/client.c

@@ -1,240 +1,44 @@
-#ifdef UA_NO_AMALGAMATION
-# include "ua_types.h"
-# include "ua_client.h"
-# include "ua_nodeids.h"
-# include "networklayer_tcp.h"
-# include "logger_stdout.h"
-# include "ua_types_encoding_binary.h"
-#else
-# include "open62541.h"
-# include <string.h>
-# include <stdlib.h>
-#endif
-
 #include <stdio.h>
 #include <stdio.h>
 
 
-void handler_TheAnswerChanged(UA_UInt32 handle, UA_DataValue *value);
-void handler_TheAnswerChanged(UA_UInt32 handle, UA_DataValue *value) {
-    printf("The Answer has changed!\n");
-    return;
-}
-
-int main(int argc, char *argv[]) {
-    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;
-    }
-    // Browse some objects
-    printf("Browsing nodes in objects folder:\n");
-
-    UA_BrowseRequest bReq;
-    UA_BrowseRequest_init(&bReq);
-    bReq.requestedMaxReferencesPerNode = 0;
-    bReq.nodesToBrowse = UA_BrowseDescription_new();
-    bReq.nodesToBrowseSize = 1;
-    bReq.nodesToBrowse[0].nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER); //browse objects folder
-    bReq.nodesToBrowse[0].resultMask = UA_BROWSERESULTMASK_ALL; //return everything
-
-    UA_BrowseResponse bResp = UA_Client_browse(client, &bReq);
-    printf("%-9s %-16s %-16s %-16s\n", "NAMESPACE", "NODEID", "BROWSE NAME", "DISPLAY NAME");
-    for (int i = 0; i < bResp.resultsSize; ++i) {
-        for (int j = 0; j < bResp.results[i].referencesSize; ++j) {
-            UA_ReferenceDescription *ref = &(bResp.results[i].references[j]);
-            if(ref->nodeId.nodeId.identifierType == UA_NODEIDTYPE_NUMERIC) {
-                printf("%-9d %-16d %-16.*s %-16.*s\n", ref->browseName.namespaceIndex,
-                       ref->nodeId.nodeId.identifier.numeric, ref->browseName.name.length,
-                       ref->browseName.name.data, ref->displayName.text.length, ref->displayName.text.data);
-            } else if(ref->nodeId.nodeId.identifierType == UA_NODEIDTYPE_STRING) {
-                printf("%-9d %-16.*s %-16.*s %-16.*s\n", ref->browseName.namespaceIndex,
-                       ref->nodeId.nodeId.identifier.string.length, ref->nodeId.nodeId.identifier.string.data,
-                       ref->browseName.name.length, ref->browseName.name.data, ref->displayName.text.length,
-                       ref->displayName.text.data);
-            }
-            //TODO: distinguish further types
-        }
-    }
-    UA_BrowseRequest_deleteMembers(&bReq);
-    UA_BrowseResponse_deleteMembers(&bResp);
-    
-#ifdef ENABLE_SUBSCRIPTIONS
-    // Create a subscription with interval 0 (immediate)...
-    UA_Int32 subId = UA_Client_newSubscription(client, 0);
-    if (subId)
-        printf("Create subscription succeeded, id %u\n", subId);
-    
-    // .. and monitor TheAnswer
-    UA_NodeId monitorThis;
-    monitorThis = UA_NODEID_STRING_ALLOC(1, "the.answer");
-    UA_UInt32 monId = UA_Client_monitorItemChanges(client, subId, monitorThis, UA_ATTRIBUTEID_VALUE, &handler_TheAnswerChanged );
-    if (monId)
-        printf("Monitoring 'the.answer', id %u\n", subId);
-    UA_NodeId_deleteMembers(&monitorThis);
-    
-    // First Publish always generates data (current value) and call out handler.
-    UA_Client_doPublish(client);
-    
-    // This should not generate anything
-    UA_Client_doPublish(client);
-#endif
-    
-    UA_Int32 value = 0;
-    // Read node's value
-    printf("\nReading the value of node (1, \"the.answer\"):\n");
-    UA_ReadRequest rReq;
-    UA_ReadRequest_init(&rReq);
-    rReq.nodesToRead = UA_ReadValueId_new();
-    rReq.nodesToReadSize = 1;
-    rReq.nodesToRead[0].nodeId = UA_NODEID_STRING_ALLOC(1, "the.answer"); /* assume this node exists */
-    rReq.nodesToRead[0].attributeId = UA_ATTRIBUTEID_VALUE;
+#include "ua_types.h"
+#include "ua_server.h"
+#include "logger_stdout.h"
+#include "networklayer_tcp.h"
 
 
-    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_INT32]) {
-        value = *(UA_Int32*)rResp.results[0].value.data;
-        printf("the value is: %i\n", value);
-    }
-
-    UA_ReadRequest_deleteMembers(&rReq);
-    UA_ReadResponse_deleteMembers(&rResp);
-
-    value++;
-    // Write node's value
-    printf("\nWriting a value of node (1, \"the.answer\"):\n");
-    UA_WriteRequest wReq;
-    UA_WriteRequest_init(&wReq);
-    wReq.nodesToWrite = UA_WriteValue_new();
-    wReq.nodesToWriteSize = 1;
-    wReq.nodesToWrite[0].nodeId = UA_NODEID_STRING_ALLOC(1, "the.answer"); /* assume this node exists */
-    wReq.nodesToWrite[0].attributeId = UA_ATTRIBUTEID_VALUE;
-    wReq.nodesToWrite[0].value.hasValue = UA_TRUE;
-    wReq.nodesToWrite[0].value.value.type = &UA_TYPES[UA_TYPES_INT32];
-    wReq.nodesToWrite[0].value.value.storageType = UA_VARIANT_DATA_NODELETE; //do not free the integer on deletion
-    wReq.nodesToWrite[0].value.value.data = &value;
-    
-    UA_WriteResponse wResp = UA_Client_write(client, &wReq);
-    if(wResp.responseHeader.serviceResult == UA_STATUSCODE_GOOD)
-            printf("the new value is: %i\n", value);
-    UA_WriteRequest_deleteMembers(&wReq);
-    UA_WriteResponse_deleteMembers(&wResp);
-
-#ifdef ENABLE_SUBSCRIPTIONS
-    // Take another look at the.answer... this should call the handler.
-    UA_Client_doPublish(client);
-    
-    // Delete our subscription (which also unmonitors all items)
-    if(!UA_Client_removeSubscription(client, subId))
-        printf("Subscription removed\n");
-#endif
-    
-#ifdef ENABLE_METHODCALLS
-    /* Note:  This example requires Namespace 0 Node 11489 (ServerType -> GetMonitoredItems) 
-       FIXME: Provide a namespace 0 independant example on the server side
-     */
-    UA_Variant input;
-    
-    UA_String argString = UA_STRING("Hello Server");
-    UA_Variant_init(&input);
-    UA_Variant_setScalarCopy(&input, &argString, &UA_TYPES[UA_TYPES_STRING]);
-    
-    UA_Int32 outputSize;
-    UA_Variant *output;
-    
-    retval = UA_Client_CallServerMethod(client, UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
-                                        UA_NODEID_NUMERIC(1, 62541), 1, &input, &outputSize, &output);
-    if(retval == UA_STATUSCODE_GOOD) {
-        printf("Method call was successfull, and %i returned values available.\n", outputSize);
-        UA_Array_delete(output, &UA_TYPES[UA_TYPES_VARIANT], outputSize);
-    } else {
-        printf("Method call was unsuccessfull, and %x returned values available.\n", retval);
-    }
-    UA_Variant_deleteMembers(&input);
-
-#endif
-
-#ifdef ENABLE_ADDNODES 
-    /* Create a new object type node */
-    // New ReferenceType
-    UA_AddNodesResponse *adResp = UA_Client_createReferenceTypeNode(client,
-        UA_EXPANDEDNODEID_NUMERIC(1, 12133), // Assign this NodeId (will fail if client is called multiple times)
-        UA_QUALIFIEDNAME(0, "NewReference"),
-        UA_LOCALIZEDTEXT("en_US", "TheNewReference"),
-        UA_LOCALIZEDTEXT("en_US", "References something that might or might not exist."),
-        UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
-        UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
-        (UA_UInt32) 0, (UA_UInt32) 0, 
-        UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
-        UA_LOCALIZEDTEXT("en_US", "IsNewlyReferencedBy"));
-    if (adResp->resultsSize > 0 && adResp->results[0].statusCode == UA_STATUSCODE_GOOD ) {
-        printf("Created 'NewReference' with numeric NodeID %u\n", adResp->results[0].addedNodeId.identifier.numeric );
-    }
-    UA_AddNodesResponse_deleteMembers(adResp);
-    free(adResp);
-    
-    // New ObjectType
-    adResp = UA_Client_createObjectTypeNode(client,    
-        UA_EXPANDEDNODEID_NUMERIC(1, 12134), // Assign this NodeId (will fail if client is called multiple times)
-        UA_QUALIFIEDNAME(0, "NewObjectType"),
-        UA_LOCALIZEDTEXT("en_US", "TheNewObjectType"),
-        UA_LOCALIZEDTEXT("en_US", "Put innovative description here."),
-        UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
-        UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
-        (UA_UInt32) 0, (UA_UInt32) 0, 
-        UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER));
-        if (adResp->resultsSize > 0 && adResp->results[0].statusCode == UA_STATUSCODE_GOOD ) {
-        printf("Created 'NewObjectType' with numeric NodeID %u\n", adResp->results[0].addedNodeId.identifier.numeric );
-    }
-    
-    // New Object
-    adResp = UA_Client_createObjectNode(client,    
-        UA_EXPANDEDNODEID_NUMERIC(1, 0), // Assign new/random NodeID  
-        UA_QUALIFIEDNAME(0, "TheNewGreatNodeBrowseName"),
-        UA_LOCALIZEDTEXT("en_US", "TheNewGreatNode"),
-        UA_LOCALIZEDTEXT("de_DE", "Hier koennte Ihre Webung stehen!"),
-        UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
-        UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
-        (UA_UInt32) 0, (UA_UInt32) 0, 
-        UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER));
-    if (adResp->resultsSize > 0 && adResp->results[0].statusCode == UA_STATUSCODE_GOOD ) {
-        printf("Created 'NewObject' with numeric NodeID %u\n", adResp->results[0].addedNodeId.identifier.numeric );
-    }
-    
-    UA_AddNodesResponse_deleteMembers(adResp);
-    free(adResp);
-    
-    // New Integer Variable
-    UA_Variant *theValue = UA_Variant_new();
-    UA_Int32 *theValueDate = UA_Int32_new();
-    *theValueDate = 1234;
-    theValue->type = &UA_TYPES[UA_TYPES_INT32];
-    theValue->data = theValueDate;
-    
-    adResp = UA_Client_createVariableNode(client,
-        UA_EXPANDEDNODEID_NUMERIC(1, 0), // Assign new/random NodeID  
-        UA_QUALIFIEDNAME(0, "VariableNode"),
-        UA_LOCALIZEDTEXT("en_US", "TheNewVariableNode"),
-        UA_LOCALIZEDTEXT("en_US", "This integer is just amazing - it has digits and everything."),
-        UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
-        UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
-        (UA_UInt32) 0, (UA_UInt32) 0, 
-        UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
-        UA_NODEID_NUMERIC(0, UA_NS0ID_INT32),
-        theValue);
-    if (adResp->resultsSize > 0 && adResp->results[0].statusCode == UA_STATUSCODE_GOOD ) {
-        printf("Created 'NewVariable' with numeric NodeID %u\n", adResp->results[0].addedNodeId.identifier.numeric );
-    }
-    UA_AddNodesResponse_deleteMembers(adResp);
-    free(adResp);
-    free(theValue);
-    /* Done creating a new node*/
-#endif
-    UA_Client_disconnect(client);
+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);
     UA_Client_delete(client);
-    return UA_STATUSCODE_GOOD;
+    return retval;
+  }
+
+  UA_DateTime raw_date = 0;
+  UA_String* string_date = UA_String_new();
+  // Read node's value
+  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;
 }
 }
-