UA Bundle SDK .NET
2.5.2.385
|
The OPC UA Demo Server delivered with the .NET SDK is a collection of examples for developing different features of an OPC UA server and contains the Unified Automation Demo address space with nodes in the Demo folder.
The source code for the Unified Automation .NET based OPC UA demo server is included in the SDK. The respective Visual Studio Solution can be found in Examples → UA Demo Server.
The FileModel class implements support for the FileType object on the server side, which can be used to give an OPC UA client access to a file managed by the OPC UA server.
The .NET OPC UA Demo Server contains an example in the Address Space in the folder Objects → Demo → 014_Files. The example code can be found in the Visual Studio Solution for the Demo Server in the file Demo → DemoNodeManager.File.cs.
The following paragraphs describe the method SetupFileObject which creates a new file object. It takes the following arguments:
UnifiedAutomation.UaServer.FileModel
UnifiedAutomation.UaServer.BaseNodeManager.LinkModelToNode
The DemoServer Address Space contains examples of the following Subtypes of the DiscreteItemType in Demo → 010_ComplianceTest → DA Profile → DataItemDiscreteType:
The DataType of an instance of TwoStateDiscreteType is Boolean. The text representing the value is stored in the properties TrueState and FalseState. So it is not necessary to prevent clients from writing values that are not allowed.
Instances of a MultiStateDiscreteType have a property called EnumStrings. The value of this property is an array of LocalizedTexts. The corresponding text for a value can be found at the array index of the value of the property. E.g. the corresponding textual representation of the value 2 can be found at the array index 2 of the value of the property.
So the allowed values for an instance of MultiStateDiscreteType are:
0 ≤ allowed value < array length.
If a client tries to write a value that is not allowed, the server has to return BadOutOfRange (see example code below). The code snippet is taken from the class EnumStringDataSource which can be found in the file Demo → ValueDataStore.cs in the Visual Studio Solution for the Demo Server.
The behavior for an instance of MultiStateValueDiscretetType is quite similar. The instances have a property called EnumValues. The value of this property contains an array of EnumValueType. I.e. the value contains an array of value-text pairs. So the server has to check wheter the value of the property contains the value to write (see example below). The code snippet is taken from the class EnumValueDataSource which can be found in the file Demo → ValueDataStore.cs in the Visual Studio Solution for the Demo Server.
The Demo Server contains an example for a variable containing localized data in the folder Objects → Demo → 011_UnicodeTest. The variable IDS_HELLO_WORLD has localizations for display name, description, and the value in English, German, and French.
The variable IDS_HELLO_WORLD is created in the method SetupLocalizedVariable which can be found in the file Demo → DemoNodeManager.cs in the Visual Studio Solution for the Demo Server.
The individual translations have to be added to the ResourceManager as shown in the following code snippet.
UnifiedAutomation.UaServer.ResourceManager
When in-memory nodes are read, the Translate method of the ResourceManager is called by the SDK automatically.
To test the translations, set the configuration parameter General.LocaleId of UaExpert to de-DE, connect to the .NET Demo Server and read the Variable. The german localizations of DisplayName, Description and Value will show up (see screenshot).
ModelChange events can be fired by the server if nodes or references are added or deleted during runtime. A client that subscribes to ModelChanegEvents is able to detect these changes to the information model. E.g. UaExpert can refresh the information model tree view automatically so that the user doesn’t need to call rebrowse manually.
The folder 008_DynamicNodes in the address space contains a property called NodeVersion. The OPC UA Specification defines that such a property is required if ModelChangeEvents are being fired for a node. In the following example, a node will be added to the folder 008_DynamicNodes, resulting in a ModelChangeEvent being fired. Thus, it is necessary to add a NodeVersion property to the folder. In our example, the property is already part of the address space imported from the XML file.
The OPC UA specification defines the method CreateDynamicNode. The .NET implementation is the method DoCreateDynamicNode which can be found in the file DemoNodeManager.Methods.cs. The following code snippet shows how to add the node.
In this example, the ModelChangeEvent is not fired directly from the method which creates the dynamic node. Instead the method AfterAddReference (which can be found in the file DemoNodeManager.cs) is used, which is called by the SDK after a reference has been added. The UpdateNodeVersion method tries to update the NodeVersion property and to fire the event.
First, the method checks the UserData wheter node versioning is supported.
If so, the node version is updated.
Finally, the event is fired.
The Demo Server contains an example that shows how to allow users to authenticate with different UserIdentityTokens. The following section gives an overview of the most important parts of the implementation. The complete source code can be found in the Visual Studio solution for the Demo Server (file TestServerManager.cs).
This event handler is called in ActivateSession and passes the IdentityToken of the client.
and
The Demo Server accepts four hard-coded UserNameIdentityTokens as listed in the following table.
UserName | Password |
---|---|
john | master |
joe | god |
sue | curly |
root | secret |
The users john, joe, and sue have been given restricted access. The code snippet shows the example code for the user john.
To give the user root administrative rights, the EffectiveIdentity is set.
The following code snippets show how to use username and password of the local windows installation for server logon.
It is also possible to use an IssuedIdentityToken or an KerberosIdentityToken for user logon. The respective checks are not shown here, but can be found in sample code.
The Demo Server contains example code for assigning access rights to nodes, which can be found in the file Demo → DemoNodeManager.AccessControl.cs and AccessControlManager.cs in the Visual Studio Solution for the Demo Server. It shows how to control the read and write access for Variable values and the browse access for specific nodes.
There are several virtual HasAccess methods defined at the BaseNodeManager. These methods control whether some specific content is send to a client. The default implementation always returns true.
In the example, all knowledge needed for access control is hard-coded. In real world applications, the information should be received by some kind of database.
The folder containing nodes with access rights that are different for specific users can be found in the folder Objects → Demo → 005_AccessRights in the Demo Server address space. There are several subfolders:
A list of users and their passwords can be found in the description of the User Authentication example.
In this example, the class AccessControlManager
To use this class, we add the AccessControlSettings to the UserData of a node (see DemoNodeManager.SetupAccessControl()). When the SDK calls the methods
the UserData is checked. If the UserData has the correct type, the HasAccess method of the AccessControlManager is called.
There are use cases where one might want to create VariableTypes that expose fields of a structure to the children of the VariableType:
There are several VariableTypes in namespace 0 having children which expose fields of the structure. An example is ServerStatusType. The DataType of its Value attribute is a structured DataType, the ServerStatusDataType. This structure has the fields StartTime, CurrentTime, State, BuildInfo SecondsTillShutdown, and ShutdownReason. For each of these fields, the ServerStatusType has a corresponding child. The instance of this type in the Demo Server address space is the ServerStatus variable of the Server object.
This example describes a simplified case which is also shown in the Demo Server: The structured DataType WorkOrderType and the corresponding WorkOrderVariableType. The instances of the latter are WorkOrderVariable and WorkOrderVariable2 in the folder Objects → Demo → 015_WorkOrder in the Demo Server Address Space.
When using such a scenario, the developer has to ensure that when the value of the instance is changed, the value of the child is changed as well (and vice versa).
The implementation can be found in the files Demo → DemoNodeManager.cs and Controllers → WorkOrderVariableModelController.cs
The Type nodes and the instance nodes are created when importing the Demo Server address space from the file demoserver.xml, which is not explained in this section.
We use the method LinkModelToNode to keep the values of the instances of WorkOrderVariableType and its children consistent (see DemoNodeManager.cs).
The remaining parts of the implementation can be found in the file WorkOrderVariableModelController.cs.
We have to extend the auto-generated class WorkOrderVariableModel by overriding GetModelHandle to assign custom getters and setters.
The LargeArrayNodeManager is an example for implementing a NodeManager without using the toolkit layer of the SDK. The nodes managed by this NodeManager can be found in the TenMillion node (Demo → 006_Massfolder_Static → TenMillion).
The class LargeArrayNodeManager demonstrates implementations for the interfaces INodeManager and IIOManager.
You can find custom implementations for
Implementing these methods also requires to implement INodeManager.GetBrowseHandle and INodeManager.GetNodeHandle. The other required Get<MethodName>Handle methods are implemented, but contain only a dummy implementation.
The Program example is a basic implementation of an OPC UA Program that counts a value down from 1000 to 0. Please read the short introduction to Programs if you’re not familiar with the concept.
The Demo Server address space contains the type CountdownStateMachineType having the child Value. This custom ProgramType is a subtype of the ProgramStateMachineType. An instance of this type can be found in the folder Demo → 016_StateMachines → Program. The sample code can be found in the file DemoNodeManager.StateMachines.cs.
For internal processing of the ProgramStateMachine we create an enumeration containing the possible states.
The generated class CountdownStateMachineModel is extended with this enumeration.
There are several transitions defined for the Program Finite State Machine. We define methods for each transition. In these methods the current state and the last transition is set.
The business logic is implemented here as well: e.g. in ReadyToRunning a timer is started that counts the the variable Value down. In addition, the TransitionEvents are fired.
To access all data that is needed to fire the event fields, we extend the generated class CountdownStateMachineModel.
The transition methods will be called by the OPC UA Method implementation. When an OPC UA Method is called, we need to check if it is possible to call the Method, since not all Methods can be called for all states of the program. If the Method can be called by the client, the transition method is called in the OPC UA Method implementation.
The methods of the ProgramState are implemented by the DemoNodeManager. So DemoNodeManager needs to implement the Interface IProgramStateMachineMethods.
After implementing the methods we need to link the CountdownStateMachineModel to the nodes in the address space.
Depending on the current state of the Program Finite State Machine, some Methods cannot be called by the client. So we set the Executable attribute to false if a Method cannot be called by a client. We add the NodeIds of the Methods as property to the class CountdownStateMachineModel and use the ServerInternalClient to get them.
The transition methods are setting the Executable attributes.
Finally, we can test the implementation with a client, e.g. UaExpert.
First we drag and drop the variables Value, CurrentState, and LastTransition to the DA View (see screenshot below). The variable CurrentState has the value Ready, because the program hasn’t been started yet. Thus, LastTransition is empty, because no transition has taken place. The value of Value is 1000, because the countdown hasn’t started yet.
Then choose Document → Add… from the menu. Select Event View from the drop-down list and confirm with OK. Drag and drop the Program object to the event view (see screenshot). We are now ready to receive transition events.
Switch back to the DA View and call the method Start. The value of CurrentState has changed to running. LastTransition has the value ReadyToRunning and the value of Value is decreasing. When switching to the Event View, we can see the transition event in the list of events.
Switch back to the DA View and call the method Suspend. The countdown stops and the values of CurrentState and LastTransition change accordingly. When calling the method Suspend again, you will get an error, because the method cannot be called in the suspended state.
Call the method Resume and wait for the program to finish if you like.