Skip to content

Commit

Permalink
Reverted client tutorial to use hl-abstraction branch. Since modifyin…
Browse files Browse the repository at this point in the history
…g node contents is covered in tutorial_nodecontents, any read/write calls (especially non-hl calls) were removed.
  • Loading branch information
ichrispa committed Sep 4, 2015
1 parent a7582b9 commit ed9d3d0
Show file tree
Hide file tree
Showing 2 changed files with 11 additions and 165 deletions.
172 changes: 9 additions & 163 deletions doc/tutorial_firstStepsClient.rst
Expand Up @@ -61,6 +61,8 @@ 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

Compiling a client is pretty much the same as compiling our server. But as you can see a client differs in a key point from a server: it has no main loop. This client will connect and disconnect in one pass, then terminate. Our server would run indefenetely. As a matter of fact, our server can so far not exit at all - we can kill the process, but that is a rather undesired way to handle a program...

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>
Expand Down Expand Up @@ -93,7 +95,10 @@ We will also make a slight change to our server: We want it to exit cleanly when
printf("Terminated\n");
return 0;
}
And then of course, recompile it::

The ``UA_Server_run()`` method is always passed a UA_Boolean and it will run until that boolean is somehow set to be *UA_FALSE*. In this example it is set when a signal is received, but any other condition may be used, including server callbacks. In a later tutorial, we will create a method that can actually shut our server down cleanly using OPC UA itself.

And then of course, recompile our server before running::

:myApp> gcc -Wl,-rpath=./ -L./ -I ./include -o myServer myServer.c -lopen62541

Expand All @@ -114,166 +119,7 @@ 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.

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) {
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;
}
//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
Conclusion
-------------
* Try to connect to some other OPC UA server by changing "opc.tcp://localhost:16664" to an appropriate address (remember that the queried node is contained in any OPC UA server).
* 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".

In our previous tutorial we focused on compiling the stack and using the compiled libraries and headers to create a basic server. In this tutorial, we covered how to create a client and how to shut down our server cleanly. We are now setup to take a closer look at server-client interaction, particularly creating and deleting nodes, :doc:`tutorial_nodes`.
4 changes: 2 additions & 2 deletions doc/tutorial_nodescontents.rst
@@ -1,7 +1,7 @@
Manipulating node attributes
============================

In our last tutorial, we created some nodes using both the server and the client side API. In this tutorial, we will explore how to manipulate the contents of nodes and create meaningful neamespace contents. This part of the tutorials focuses in particular on node fields (displayname, description,...) and variables.
In our last tutorial, :doc:`tutorial_nodes`, we created some nodes using both the server and the client side API. In this tutorial, we will explore how to manipulate the contents of nodes and create meaningful neamespace contents. This part of the tutorials focuses in particular on node fields (displayname, description,...) and variables.

Getting and setting node attributes
-----------------------------------
Expand Down Expand Up @@ -311,4 +311,4 @@ Callbacks and handles are a very important concept of open62541 and we will enco
Conclusion
----------

In this tutorial you have learned how to harness variable contents to do your bidding. You can now create dynamic read/write callbacks that can update your data contents on the fly, even if the server is running its main loop.
In this tutorial you have learned how to harness variable contents to do your bidding. You can now create dynamic read/write callbacks that can update your data contents on the fly, even if the server is running its main loop. However, our nodes are out of sync with object oriented concepts - we are not using any of the Type/Inheritance/Objectmodel strengths that OPC UA provides yet. We will mend that in our next tutorial, :doc:`tutorial_noderelations`.

0 comments on commit ed9d3d0

Please sign in to comment.