High Performance OPC UA Server SDK
1.7.1.383
|
This lesson explains how to create a custom provider to extend the address space and serve values using value stores.
Files used in this lesson:
The real world example used for this getting started lesson is an arbitrary device that contains three 32 bit unsigned integer variables. The variables will be represented by three UA nodes in the address space to allow read and write access to the device. As the device is not actually available, the variables are kept in memory for this example.
This lesson will introduce value stores, these are integrated in the SDK and offer easy access to values using different mechanisms. The implementation of a value store to access a device is much simpler than implementing service handlers for read, write, and subscription, but the value store is limited to devices with synchronous access to the data.
This lesson shows how to implement a custom value store to access the example device and how to use a value store for in-memory data implemented by the SDK.
The new custom provider is implemented in custom_provider.c and needs an initialization function:
This function is added to the provider array in server_main.c.
Inside the init function the provider registers its address space. A new empty address space for the given URL is created, and the SDK assigned index of the namespace is returned and stored in a global variable. The ua_addressspace_register function takes a configuration structure with the maximum number of nodes and references that can be created inside the new namespace:
The new namespace index must be registered at the provider management:
The custom store must be directly registered at the global valuestore management. The second parameter of ua_valuestore_register_store is an in-out parameter with the requested storeindex and is assigned the actual storeindex. Here again zero is used to automatically assign a free index.
The implementation of the accessor functions passed to the registration will be shown in the next step.
Now the nodes of the custom provider can be created. This happens in an own function custom_provider_create_nodes that will be covered later in this lesson:
The service handler functions in ctx
are already pre-configured to use the SDK internal functions. It is possible for replace the default implementations with an own implementation for advanced users, but normally your will keep this. The default implementation uses the store interface to access the variable values.
It is also possible to register a cleanup function that is called when the server shuts down. In this example it is used to unregister the value stores:
The value stores work with two indices: The store index is used to identify instances of value stores, the value index is used to identify a value inside a store and has a store specific meaning. Both indices are saved in variable nodes, and the SDK uses them find the associated store for the node and retrieve or write a value.
The custom store for the device is implemented in custom_provider_store.c and the functions are declared in custom_provider_store.h. The values of the device are directly saved in a global array of a custom structure, where the value index is used as array index. For a real device, this could also be an array of port numbers or device specific addresses:
There is a function to set the initial value for an index:
The other two functions are the accessor functions to get and set values, which are defined by the store.
There is a getter function for reading a value from the store taking the following parameters:
Implementing the function is simple: The value index must be checked to not read out of bounds of the array, and reading with index range is not possible as this is a scalar. To determine whether the value or the eu range shall be read the browsename of the node is evaluated. Then the value and source timestamp are set (if required), and the status code is set to good. If an error happens during reading the value, the status code of the result is set to a bad status code and the function returns:
There is also a function for writing a value to the store. The parameters are similiar to the getter function, but instead of the result, the value to write is passed. Furthermore, in case of an error a bad status code must be passed back as return value.
The function itself again needs to check the value index and the index range. Then it has to make sure that only the value is written and no timestamp or status code. Finally the type of the value must be checked, and the value can be written to the corresponding slot of the array:
The function is called attach because it may also take the ownership of the value by creating a shallow copy and remove the value from the original ua_datavalue struct:
The values from the device should be represented by variable nodes in the address space for being accessible for clients like UaExpert. Furthermore, each variable will get a property indicating its valid range. This happens in custom_provider_nodes.c
To create multiple variable nodes, there is a function to create a single variable node that will be called multiple times. This function creates a new node from the node class variable using ua_node_create_with_attributes. The new node will get a type definition reference to basedatavariabletype and is referenced by its parent node with an organizes reference. Further attributes specific to the variable node are set with ua_variable_set_attributes.
Then the initial value is set with the function implemented in Step 2: Create a Custom Value Store and the node is connected to the value by setting the store index and value index:
Creating and adding a property works similar to creating the variable node in Create a Variable Node, because the property is also a variable node. So the same two functions can be used to create a new node and set the variable attributes. The main differences are the type definition and that the parent references this node with a hasproperty reference. Also note that the namespace index of the browse name is zero:
The property should contain a value from type range that indicates the minimum and maximum value the variable can have. However, this minimum is not enforced by the implementation and purely informative. The value index is retrieved from the parent variable node as the eu_range is stored in the same structure as the value. Than this index is attached to the node of the eu range.
Now a new folder for the custom nodes can be created by calling ua_object_create_folder and the new nodes are created using the functions implemented previously in Create a Variable Node and Create a Property Node
Compile and run the server application. When connecting to the server with a UA Client (e.g. UaExpert) the newly created nodes are visible in the server’s address space. It is also possible to read and subscribe to values and write to the variables.