High Performance OPC UA Server SDK
1.1.1.177
|
This lesson will guide you through the process of setting up a basic OPC UA Client console application.
The application will connect to a server, read the values of two variable nodes, browse two nodes for references and disconnect.
Files used in this lesson:
The recommended way of building applications with the SDK is using CMake. This and the further lessons rely on CMake being installed on the build machine.
The CMakeLists.txt is similar to the server example, but instead of the SDK_SERVER_LIBRARIES the SDK_CLIENT_LIBRARIES are needed to link a client application. In case of linking a combined client and server application, both libraries would be required.
The Sample Client consists of the public header sampleclient.h and the implementation in sampleclient.c.
There is a struct to hold the context information of the Sample Client:
It is possible to create multiple instance of the Sample Client in one application by allocating multiple of these structs. To keep it simple this example only creates only one instance.
There are also functions to operate on this struct:
The implementation of these file will be explained later on.
The SDK provided client functions are asynchronous, so for a clear implementation the Sample Client has its own simple state machine. The states are defined by an enum:
The states are processed one after each other in the same order as they are defined. Only in case of error some states are skipped and the state machine jumps directly to DISCONNECT or FINISHED depending on wether the client is already connected when the error occurs. To make sure actions are triggered sample_client_check_state() must be called regularly:
This function returns true as long as there is still work to do and false to indicate it has finished.
Before starting the connect the Sample Client is initialized, here the SDK client member is initialized and callbacks are registered. For registering callbacks the struct is first completely set to zero and then the implemented function pointers are set. This is recommended as further callbacks might be added in future versions and this allows you code to stay compatible. Also the Sample Client context is set as userdata, so the context can be accessed with ua_client_get_userdata() from any client callback.
The registered callback function is called every time the connection status of the client changes. It is used for pure information here:
After initializing the client now the connect operation can be started. All operations in the client SDK that involve network communication with the server are asynchronous. These functions can be easily recognized by the word 'begin' in their function name, also they have usually a callback function and callback data as parameters. It is important to check their return value, as in case of error the callback function is not called and the caller is responsible to free any memory passed to the function. In case of success the callback function is always called.
So starting the connect operation is rather simple, ua_client_begin_connect() is called and the synchronous result is checked to set the next state of the Sample Client:
The associated callback function gets as first argument the client context used for the begin call, ua_client_get_userdata() could be used to get the Sample Client context, but in this case the context was given as the callback data, so it is enough to cast the second argument to the correct pointer type.
The result of the operation is passed in the last two arguments. There is an int that has one of the values from <common/errors.h> to indicate an error in the client SDK, e.g. out of memory or a reponse could not be decoded. This is the first result to check, when it is zero (good) then the ua_statuscode argument must be checked. It contains the result that was generated and sent by the server in its response. In the example both results are checked and the state is set accordingly:
Next the Sample Client will read the values of two variable nodes from the server using the OPC UA read service. For most services there are functions available in <uaclient/uaclient_services.h> which work all very similar, except for the type of the request parameter of course. So for the read service ua_client_begin_read() is used.
The ua_readrequest struct can be simply put on the stack, but it is important the members are allocated with ipc_malloc (or a function that uses ipc_malloc). On success ua_client_begin_read() will make a shallow copy of the request and so take ownership of the members.
The callback function receives the the client result which must be checked first, only if it is good, the response header is guaranteed to be present, else it might be NULL. Then again only if the service result inside the response header is good, the response is guaranteed to be present. The parameters of the callback functions are the same for all services, except for the type of the response.
After the checks the Sample Client prints all received values. The content of the response is freed by the client SDK after the callback, but for a more sophisticated application that still needs to use parts of the response it is possible to detach values, and set the original values to zero.
The callback also contains the ua_readrequest as parameter, please note that it is only set when the service setting keep_request is set to true. In this case the request can be accessed in if its information is still useful or should be detached.
The browse service is also available from <uaclient/uaclient_services.h> via ua_client_begin_browse() and ua_client_begin_browsenext(). However using these to functions to handle continuation points properly is rather difficult. For this reason this client SDK offers an easier way to browse: ua_client_begin_simple_browse() from <uaclient/uaclient_simple_browse.h>. This function handles continuation points and various recoverable errors in a transparent way. It can be called with the complete array of nodes to browse and returns a response with the complete results in its callback function.
The code to start the browse operation is similar to the read above:
The callback function is also similar to the above. There are two differences: First the result can be a combination of multiple service calls, so the there is no response header, but only the servcie result. Second the request is always set in the callback, this can be used to detach values that were allocated for the ua_client_begin_simple_browse() call.
The Sample Client again prints all results:
Disconnecting from the server involves multiple network messages, so the disconnect operation is asynchronous, too. Calling ua_client_begin_disconnect() is rather simple:
The callback function checks the result, however the state is always changed to FINISH:
After disconnect the Sample Client provides a function to get the result:
And a function to cleanup:
The main.c contains the main function that is called at application startup. It parses the command line input, initializes the application, keeps the program running and cleans up.
First there are some global variables, the static variables could also be put at the beginnig of the main function or be dynamically allocated, but we do not want to waste stack space and keep dynamic allocations at a minimum. The g_appconfig needs to be global as it holds the configuration and is accessed by multiple components of the SDK.
The main function starts with parsing command line agruments and initializing the uaapplication context and Sample Client context:
Next is the main loop, from here all actions are run. To trigger actions in the SDK uaapplication_timed_docom() must be called regularly. It processes network message, encoding and is responsible for calling callback functions. For the Sample Client sample_client_check_state() must be called to progress in its state machine. The main loop runs until the Sample Client indicates it is finished.
After that the result of the Sample Client is evaluated and the Sample CLient and uaaplication context is cleaned up. Some of the global variables are set to zero to help detecting memory leaks with tools like valgrind.
To build a client application it is important to enable the cmake option ENABLE_CLIENT_FUNCTIONALITY. If your application is a standalone client without a server, the option ENABLE_SERVER_FUNCTIONALITY can be disabled.
CMake needs to know the source folder and the build folder for the project. The source folder is the lesson01 folder where the above mentioned files are stored (you can also use the higher level client_gettingstarted folder to build all lessons at once). The build folder is the folder where the executable will be built, e.g. a newly created folder inside or next to the source folder.
Depending on your platform you can choose one of the following options:
To start the client the settings.conf file must be in the same folder as the built executable. Furthermore, the openssl dynamic libraries are needed. These must be either in the default search path of the system or also next to the executable.
Before starting the client a server must be running which allows the NONE security policy and Anonymous users. The client will by default connect to the url in the top of main.c (opc.tcp://localhost:4840). To connect to a different server the command line parameter -u can be used, for example '-u opc.tcp://my-server:48010'.